Skip to main content

Command Palette

Search for a command to run...

Complete Guide to GraphQL with Apollo Server and MongoDB

Published
โ€ข6 min read
Complete Guide to GraphQL with Apollo Server and MongoDB
P

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.

โœ… 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.

๐Ÿ“š Resources