GraphQL + TS + Express + MongoDB
This documentation shows how to build a complete GraphQL API using Express, express-graphql (not Apollo), TypeScript, and MongoDB with Mongoose.
The final API supports CRUD operations for Users.
When finished, your GraphiQL will be available at: http://localhost:4000/graphql (in non-production).
Project Structure
A clean structure improves maintainability. We'll separate schema, resolvers, controllers and models.
graphql-mongo-server/
│
├── src/
│ ├── index.ts ← Server entrypoint
│ ├── config/
│ │ └── db.ts ← MongoDB connection setup
│ ├── schema/
│ │ ├── index.ts ← Merges all schemas
│ │ └── userSchema.ts ← User schema (TypeDefs)
│ ├── resolvers/
│ │ ├── index.ts ← Merges all resolvers
│ │ └── userResolver.ts ← User resolver logic
│ ├── models/
│ │ └── userModel.ts ← Mongoose User model
│ ├── types/
│ │ └── user.ts ← TypeScript User interface
│ └── controllers/
│ └── userController.ts ← User CRUD logic
│
├── .env
├── package.json
├── tsconfig.json
└── README.md
Installation & Setup
Initialize the project, install dependencies, and configure TypeScript.
1) Initialize Project
mkdir graphql-mongo-server
cd graphql-mongo-server
npm init -y
2) Install Dependencies
Main dependencies (pinned for express-graphql compatibility):
npm install express cors dotenv mongoose express-graphql graphql@^15.8.0 @graphql-tools/schema @graphql-tools/merge
Dev dependencies:
npm install -D typescript ts-node-dev @types/node @types/express @types/cors
3) TypeScript Config (tsconfig.json)
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": ["src"]
}
4) package.json Scripts
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/index.ts",
"build": "tsc -p tsconfig.json",
"start": "node dist/index.js",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
Database Config
.env
PORT=4000
MONGODB_URI=mongodb://127.0.0.1:27017/graphql_mongo_server
NODE_ENV=development
src/config/db.ts
Imports: Import mongoose for database connection and dotenv to read environment variables.
import mongoose from "mongoose";
import dotenv from "dotenv";
dotenv.config();
Connection Function: An async function that connects to MongoDB via URI from .env file and validates its existence.
export async function connectToDatabase(): Promise<void> {
const mongoUri = process.env.MONGODB_URI;
if (!mongoUri) {
throw new Error("MONGODB_URI is not set in environment variables");
}
mongoose.set("strictQuery", true);
await mongoose.connect(mongoUri);
}
Server Entrypoint
src/index.ts
Imports: Import all necessary libraries and components to run the GraphQL server.
import express from "express";
import cors from "cors";
import dotenv from "dotenv";
import { graphqlHTTP } from "express-graphql";
import { makeExecutableSchema } from "@graphql-tools/schema";
import { typeDefs } from "./schema";
import { resolvers } from "./resolvers";
import { connectToDatabase } from "./config/db";
dotenv.config();
Express Setup: Create Express app and add middleware for CORS and JSON parsing.
const app = express();
app.use(cors());
app.use(express.json());
Create Schema and GraphQL Endpoint: Merge typeDefs and resolvers to create GraphQL schema, then add /graphql route with GraphiQL enabled in development.
const schema = makeExecutableSchema({ typeDefs, resolvers });
app.use(
"/graphql",
graphqlHTTP({
schema,
graphiql: process.env.NODE_ENV !== "production"
})
);
Start Server: Connect to database first, then start listening on the specified port.
const port = process.env.PORT || 4000;
connectToDatabase()
.then(() => {
app.listen(port, () => {
console.log(`GraphQL server running on http://localhost:${port}/graphql`);
});
})
.catch((err) => {
console.error("Failed to start server:", err);
process.exit(1);
});
Models & Types
TypeScript Types
src/types/user.ts
User Interface: Define TypeScript interface to represent user data in the application.
export interface User {
id: string;
name: string;
email: string;
createdAt?: string;
updatedAt?: string;
}
Mongoose Models
src/models/userModel.ts
Imports and Interface: Import mongoose and create UserDocument interface that extends Document.
import mongoose, { Schema, Document, Model } from "mongoose";
export interface UserDocument extends Document {
name: string;
email: string;
}
Define Schema: Create Mongoose Schema with name and email fields along with validation and sanitization options.
const UserSchema: Schema<UserDocument> = new Schema(
{
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true, trim: true }
},
{ timestamps: true }
);
Export Model: Create and export Mongoose model for User.
export const UserModel: Model<UserDocument> = mongoose.model<UserDocument>("User", UserSchema);
Controllers (Logic)
src/controllers/userController.ts
Import: Import UserModel to use in CRUD operations.
import { UserModel } from "../models/userModel";
Read Functions: Two functions to get all users or a single user by ID.
export const userController = {
async getUsers() { return UserModel.find().exec(); },
async getUserById(id: string) { return UserModel.findById(id).exec(); },
Create Function: Create a new user using the provided input data.
async createUser(input: { name: string; email: string }) {
return new UserModel(input).save();
},
Update Function: Update existing user data using ID and new field values.
async updateUser(id: string, input: { name?: string; email?: string }) {
return UserModel.findByIdAndUpdate(id, input, { new: true }).exec();
},
Delete Function: Delete a user by ID and return true if deletion was successful.
async deleteUser(id: string) {
return Boolean(await UserModel.findByIdAndDelete(id).exec());
}
};
GraphQL Schema (TypeDefs)
TypeDefs
src/schema/userSchema.ts
User Type: Define GraphQL type for User with all required fields.
export const userTypeDefs = /* GraphQL */ `
type User {
id: ID!
name: String!
email: String!
createdAt: String
updatedAt: String
}
Input Types: Define input types for creating and updating users.
input CreateUserInput { name: String!, email: String! }
input UpdateUserInput { name: String, email: String }
Query Type: Define read operations (GET) for users.
type Query {
users: [User!]!
user(id: ID!): User
}
Mutation Type: Define write operations (CREATE, UPDATE, DELETE) for users.
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User
deleteUser(id: ID!): Boolean!
}
`;
Root Schema
src/schema/index.ts
Merge Schemas: Use mergeTypeDefs to combine all typeDefs into a single schema.
import { mergeTypeDefs } from "@graphql-tools/merge";
import { userTypeDefs } from "./userSchema";
export const typeDefs = mergeTypeDefs([userTypeDefs]);
GraphQL Resolvers
Resolver Logic
src/resolvers/userResolver.ts
Import: Import userController to use CRUD functions.
import { userController } from "../controllers/userController";
Query Resolvers: Resolvers for read operations (users and user).
export const userResolvers = {
Query: {
users: async () => userController.getUsers(),
user: async (_: any, args: { id: string }) => userController.getUserById(args.id)
},
Mutation Resolvers: Resolvers for write operations (createUser, updateUser, deleteUser).
Mutation: {
createUser: async (_: any, args: { input: { name: string; email: string } }) =>
userController.createUser(args.input),
updateUser: async (_: any, args: { id: string; input: { name?: string; email?: string } }) =>
userController.updateUser(args.id, args.input),
deleteUser: async (_: any, args: { id: string }) => userController.deleteUser(args.id)
}
};
Root Resolver
src/resolvers/index.ts
Merge Resolvers: Use mergeResolvers to combine all resolvers into a single resolver.
import { mergeResolvers } from "@graphql-tools/merge";
import { userResolvers } from "./userResolver";
export const resolvers = mergeResolvers([userResolvers]);
Running the Project
- Ensure MongoDB is running locally or provide a remote URI in
.env. - Install dependencies:
npm install - Create
.envas shown above. - Start dev server:
npm run dev
Open http://localhost:4000/graphql for GraphiQL (not enabled in production).
Example Queries
Create User
Create New User: Use mutation to create a new user and return requested data.
mutation {
createUser(input: { name: "Ali", email: "ali@example.com" }) {
id
name
email
}
}
Get All Users
Get All Users: Use query to retrieve a list of all users.
query {
users {
id
name
email
}
}
Get Single User
Get Single User: Use query with ID to get a specific user.
query {
user(id: "USER_ID") {
id
name
email
}
}
Update User
Update User: Use mutation to update an existing user's data.
mutation {
updateUser(id: "USER_ID", input: { name: "Updated Name" }) {
id
name
email
}
}
Delete User
Delete User: Use mutation to delete a user by ID.
mutation {
deleteUser(id: "USER_ID")
}