Complete Guide to GraphQL with Apollo Server and MongoDB

MERN STACK DEVELOPER
GraphQL is one of the most exciting technologies in modern web development. It gives frontend developers the power to request exactly the data they need while keeping backend APIs clean, efficient, and type-safe.
In this blog, weโll go step by step โ from building a simple GraphQL API with an in-memory array to connecting it with a MongoDB database. Then, weโll dive into advanced concepts like input types, error handling, pagination, authentication, and subscriptions.
By the end, youโll be able to build and scale a real-world GraphQL API. ๐
1. Introduction
๐น What is GraphQL?
GraphQL is a query language for APIs and a runtime for executing those queries. It was developed by Facebook to solve limitations of REST APIs.
๐น GraphQL vs REST
REST: Multiple endpoints (
/users,/products,/orders) โ Over-fetching or under-fetching data is common.GraphQL: Single endpoint (
/graphql) โ Clients request exactly the data they want in one query.
Example:
query {
product(id: 1) {
name
price
}
}
This returns only name and price โ nothing more, nothing less.
๐น Why is GraphQL popular?
โ
Single endpoint
โ
Flexible queries
โ
Strongly typed schema
โ
Great developer experience
2. Core Concepts:
Before coding, letโs understand the building blocks of GraphQL.
Schema โ Defines the types of data and operations (Query, Mutation).
Queries โ Used to fetch data.
Mutations โ Used to modify data (create, update, delete).
Resolvers โ Functions that tell GraphQL how to fetch the actual data.
SDL (Schema Definition Language) โ Syntax to define GraphQL schema.
Example SDL:
type Product {
id: ID!
name: String!
price: Float!
}
type Query {
products: [Product!]!
product(id: ID!): Product
}
3. CRUD Example (Without DB)
Letโs start by creating a simple GraphQL API with Apollo Server and an in-memory array.
๐น Install dependencies
npm init -y
npm install @apollo/server graphql graphql-tag
๐น Setup Apollo Server (server.js)
const { ApolloServer } = require("@apollo/server");
const { startStandaloneServer } = require("@apollo/server/standalone");
const typeDefs = require("./graphql/schema");
const resolvers = require("./graphql/resolver");
async function startServer() {
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`๐ Server ready at: ${url}`);
}
startServer();
๐น Define Schema (graphql/schema.js)
const { gql } = require("graphql-tag");
const typeDefs = gql`
type Product {
id: ID!
name: String!
price: Float!
category: String!
inStock: Boolean!
}
type Query {
products: [Product!]!
product(id: ID!): Product
}
type Mutation {
createProduct(
name: String!
price: Float!
category: String!
inStock: Boolean!
): Product
deleteProduct(id: ID!): Boolean
updateProduct(
id: ID!
name: String
price: Float
category: String
inStock: Boolean
): Product
}
`;
module.exports = typeDefs;
๐น Create Resolvers (graphql/resolver.js)
const products = [
{ id: "1", name: "Laptop", price: 999.99, category: "Electronics", inStock: true },
{ id: "2", name: "Smartphone", price: 699.99, category: "Electronics", inStock: true },
{ id: "3", name: "Headphones", price: 199.99, category: "Accessories", inStock: true },
];
const resolvers = {
Query: {
products: () => products,
product: (_, { id }) => products.find((p) => p.id === id),
},
Mutation: {
createProduct: (_, { name, price, category, inStock }) => {
const newProduct = { id: String(products.length + 1), name, price, category, inStock };
products.push(newProduct);
return newProduct;
},
deleteProduct: (_, { id }) => {
const index = products.findIndex((p) => p.id === id);
if (index === -1) return false;
products.splice(index, 1);
return true;
},
updateProduct: (_, { id, ...updated }) => {
const index = products.findIndex((p) => p.id === id);
if (index === -1) return null;
products[index] = { ...products[index], ...updated };
return products[index];
},
},
};
module.exports = resolvers;
๐น Try it in Playground
Start the server:
node server.js
Run queries at http://localhost:4000
โ Fetch all products
query {
products {
id
name
}
}
โ Fetch one product
query {
product(id: "2") {
name
price
}
}
โ Create a product
mutation {
createProduct(
name: "Desk Chair"
price: 149.99
category: "Furniture"
inStock: true
) {
id
name
}
}
4. Moving to Database (With MongoDB)
Using arrays is fine for learning, but we need persistent storage.
Letโs use MongoDB + Mongoose.
๐น Install Mongoose
npm install mongoose
๐น Define Model (models/Product.js)
const mongoose = require("mongoose");
const productSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
description: { type: String, required: true },
price: { type: Number, required: true, min: 0 },
category: { type: String, required: true },
stock: { type: Number, required: true, min: 0 },
createdAt: { type: Date, default: Date.now },
});
const Product = mongoose.model("Product", productSchema);
module.exports = Product;
๐น Connect to DB (server.js)
const mongoose = require("mongoose");
async function startServer() {
await mongoose.connect("mongodb://localhost:27017/graphql-products");
const server = new ApolloServer({ typeDefs, resolvers });
const { url } = await startStandaloneServer(server, { listen: { port: 4000 } });
console.log(`๐ Server ready at: ${url}`);
}
๐น Update Resolvers (graphql/resolver.js)
const Product = require("../models/Product");
const resolvers = {
Query: {
products: async () => await Product.find(),
product: async (_, { id }) => await Product.findById(id),
},
Mutation: {
createProduct: async (_, args) => {
const newProduct = new Product(args);
return await newProduct.save();
},
deleteProduct: async (_, { id }) => {
const res = await Product.findByIdAndDelete(id);
return !!res;
},
updateProduct: async (_, { id, ...updated }) => {
return await Product.findByIdAndUpdate(id, updated, { new: true });
},
},
};
module.exports = resolvers;
๐ Now your data is stored in MongoDB permanently!
5. Advanced Schema Features
GraphQL schemas can do much more:
๐น Input Types
Instead of passing many arguments:
input ProductInput {
name: String!
price: Float!
category: String!
inStock: Boolean!
}
type Mutation {
createProduct(data: ProductInput!): Product
}
๐น Enums
Restrict values:
enum Category {
ELECTRONICS
FURNITURE
ACCESSORIES
}
๐น Scalars
GraphQL has built-in types: ID, String, Int, Float, Boolean. You can also define custom scalars (like Date).
6. Error Handling
Always use try/catch in resolvers:
createProduct: async (_, args) => {
try {
const newProduct = new Product(args);
return await newProduct.save();
} catch (err) {
throw new Error("Failed to create product: " + err.message);
}
}
7. Pagination & Filtering
Example query with pagination:
type Query {
products(limit: Int, offset: Int): [Product!]!
}
Resolver:
products: async (_, { limit = 10, offset = 0 }) =>
await Product.find().skip(offset).limit(limit),
Filtering:
products(category: String): [Product!]!
8. Authentication & Authorization
You can secure GraphQL APIs with JWT:
User logs in โ gets a token.
Token is passed in headers.
Resolvers check authentication.
context: ({ req }) => {
const token = req.headers.authorization || "";
const user = verifyToken(token);
return { user };
}
9. Subscriptions (Real-time GraphQL)
GraphQL Subscriptions allow real-time updates over WebSockets.
Example: notify clients when a new product is created.
(Apollo supports subscriptions with graphql-ws.)
10. Best Practices
Keep schema modular (
schema/product.js,schema/user.js).Use DataLoader to batch DB requests.
Avoid breaking changes โ version your schema carefully.
11. Conclusion
We started with:
โ
Apollo Server setup
โ
Schema, queries, and mutations
โ
In-memory data โ MongoDB persistence
โ
Advanced features (inputs, enums, error handling, pagination, auth, subscriptions)
GraphQL is worth learning because it makes APIs flexible, efficient, and future-proof.


