Back to Tutorial Overview

GraphQL API Development - Complete Guide

Build powerful GraphQL APIs with Apollo Server and Node.js

Section 1: Project Setup

Initialize GraphQL Project

# Create project
mkdir graphql-api
cd graphql-api
npm init -y

# Install dependencies
npm install @apollo/server graphql
npm install express cors
npm install mongoose dotenv
npm install --save-dev typescript @types/node

# Initialize TypeScript
npx tsc --init

Section 2: Defining GraphQL Schema

Type Definitions

// src/schema/typeDefs.ts
import { gql } from 'apollo-server-express';

export const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!
  }

  type Post {
    id: ID!
    title: String!
    content: String!
    published: Boolean!
    author: User!
    createdAt: String!
    updatedAt: String!
  }

  type AuthPayload {
    token: String!
    user: User!
  }

  type Query {
    # Users
    users: [User!]!
    user(id: ID!): User
    me: User

    # Posts
    posts: [Post!]!
    post(id: ID!): Post
    postsByUser(userId: ID!): [Post!]!
  }

  type Mutation {
    # Auth
    signup(name: String!, email: String!, password: String!): AuthPayload!
    login(email: String!, password: String!): AuthPayload!

    # Users
    updateUser(id: ID!, name: String, email: String): User!
    deleteUser(id: ID!): Boolean!

    # Posts
    createPost(title: String!, content: String!, published: Boolean): Post!
    updatePost(id: ID!, title: String, content: String, published: Boolean): Post!
    deletePost(id: ID!): Boolean!
  }

  type Subscription {
    postCreated: Post!
    postUpdated: Post!
  }
`;

Section 3: Implementing Resolvers

Query Resolvers

// src/resolvers/resolvers.ts
import User from '../models/User';
import Post from '../models/Post';
import jwt from 'jsonwebtoken';

export const resolvers = {
  Query: {
    // Get all users
    users: async () => {
      return await User.find();
    },

    // Get single user
    user: async (_: any, { id }: { id: string }) => {
      return await User.findById(id);
    },

    // Get current user
    me: async (_: any, __: any, context: any) => {
      if (!context.user) {
        throw new Error('Not authenticated');
      }
      return await User.findById(context.user.id);
    },

    // Get all posts
    posts: async () => {
      return await Post.find().populate('author');
    },

    // Get single post
    post: async (_: any, { id }: { id: string }) => {
      return await Post.findById(id).populate('author');
    },

    // Get posts by user
    postsByUser: async (_: any, { userId }: { userId: string }) => {
      return await Post.find({ author: userId }).populate('author');
    },
  },

  Mutation: {
    // Signup
    signup: async (_: any, { name, email, password }: any) => {
      const existingUser = await User.findOne({ email });
      if (existingUser) {
        throw new Error('User already exists');
      }

      const user = await User.create({ name, email, password });
      const token = jwt.sign(
        { id: user._id },
        process.env.JWT_SECRET!,
        { expiresIn: '7d' }
      );

      return { token, user };
    },

    // Login
    login: async (_: any, { email, password }: any) => {
      const user = await User.findOne({ email }).select('+password');
      if (!user) {
        throw new Error('Invalid credentials');
      }

      const isValid = await user.comparePassword(password);
      if (!isValid) {
        throw new Error('Invalid credentials');
      }

      const token = jwt.sign(
        { id: user._id },
        process.env.JWT_SECRET!,
        { expiresIn: '7d' }
      );

      return { token, user };
    },

    // Create post
    createPost: async (
      _: any,
      { title, content, published = false }: any,
      context: any
    ) => {
      if (!context.user) {
        throw new Error('Not authenticated');
      }

      const post = await Post.create({
        title,
        content,
        published,
        author: context.user.id
      });

      return await post.populate('author');
    },

    // Update post
    updatePost: async (
      _: any,
      { id, title, content, published }: any,
      context: any
    ) => {
      if (!context.user) {
        throw new Error('Not authenticated');
      }

      const post = await Post.findById(id);
      if (!post) {
        throw new Error('Post not found');
      }

      if (post.author.toString() !== context.user.id) {
        throw new Error('Not authorized');
      }

      return await Post.findByIdAndUpdate(
        id,
        { title, content, published },
        { new: true }
      ).populate('author');
    },

    // Delete post
    deletePost: async (_: any, { id }: { id: string }, context: any) => {
      if (!context.user) {
        throw new Error('Not authenticated');
      }

      const post = await Post.findById(id);
      if (!post) {
        throw new Error('Post not found');
      }

      if (post.author.toString() !== context.user.id) {
        throw new Error('Not authorized');
      }

      await Post.findByIdAndDelete(id);
      return true;
    },
  },

  // Field resolvers
  User: {
    posts: async (parent: any) => {
      return await Post.find({ author: parent.id });
    },
  },

  Post: {
    author: async (parent: any) => {
      return await User.findById(parent.author);
    },
  },
};

Section 4: Setting Up Apollo Server

Server Configuration with Authentication

// src/server.ts
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import express from 'express';
import cors from 'cors';
import jwt from 'jsonwebtoken';
import { typeDefs } from './schema/typeDefs';
import { resolvers } from './resolvers/resolvers';
import connectDB from './config/database';

const app = express();
const PORT = process.env.PORT || 4000;

// Connect to database
connectDB();

// Create Apollo Server
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

// Start server
async function startServer() {
  await server.start();

  app.use(
    '/graphql',
    cors(),
    express.json(),
    expressMiddleware(server, {
      context: async ({ req }) => {
        // Get token from header
        const token = req.headers.authorization?.replace('Bearer ', '');
        
        if (token) {
          try {
            const user = jwt.verify(token, process.env.JWT_SECRET!);
            return { user };
          } catch (err) {
            return {};
          }
        }
        
        return {};
      },
    })
  );

  app.listen(PORT, () => {
    console.log(`🚀 Server ready at http://localhost:${PORT}/graphql`);
  });
}

startServer();

Section 5: GraphQL Queries & Mutations

Example Queries

# Get all users
query GetUsers {
  users {
    id
    name
    email
    posts {
      id
      title
    }
  }
}

# Get single user with posts
query GetUser {
  user(id: "USER_ID") {
    id
    name
    email
    posts {
      id
      title
      content
      published
      createdAt
    }
  }
}

# Get all posts with authors
query GetPosts {
  posts {
    id
    title
    content
    published
    author {
      id
      name
      email
    }
    createdAt
  }
}

# Get current user
query Me {
  me {
    id
    name
    email
    posts {
      id
      title
    }
  }
}

Example Mutations

# Signup
mutation Signup {
  signup(
    name: "John Doe"
    email: "john@example.com"
    password: "password123"
  ) {
    token
    user {
      id
      name
      email
    }
  }
}

# Login
mutation Login {
  login(
    email: "john@example.com"
    password: "password123"
  ) {
    token
    user {
      id
      name
      email
    }
  }
}

# Create post (requires authentication)
mutation CreatePost {
  createPost(
    title: "My First Post"
    content: "This is my first GraphQL post!"
    published: true
  ) {
    id
    title
    content
    author {
      name
    }
    createdAt
  }
}

# Update post
mutation UpdatePost {
  updatePost(
    id: "POST_ID"
    title: "Updated Title"
    published: true
  ) {
    id
    title
    content
    updatedAt
  }
}

# Delete post
mutation DeletePost {
  deletePost(id: "POST_ID")
}

Section 6: Advanced GraphQL Features

Pagination with Cursor-Based Approach

// Add to typeDefs
type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
}

type PostEdge {
  cursor: String!
  node: Post!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

extend type Query {
  postsPaginated(first: Int, after: String): PostConnection!
}

// Resolver
postsPaginated: async (_: any, { first = 10, after }: any) => {
  const query = after ? { _id: { $gt: after } } : {};
  
  const posts = await Post.find(query)
    .limit(first + 1)
    .sort({ _id: 1 })
    .populate('author');

  const hasNextPage = posts.length > first;
  const edges = posts.slice(0, first).map(post => ({
    cursor: post._id.toString(),
    node: post
  }));

  return {
    edges,
    pageInfo: {
      hasNextPage,
      hasPreviousPage: !!after,
      startCursor: edges[0]?.cursor,
      endCursor: edges[edges.length - 1]?.cursor
    }
  };
}

DataLoader for N+1 Problem

import DataLoader from 'dataloader';

// Create DataLoader for batching
const userLoader = new DataLoader(async (userIds: string[]) => {
  const users = await User.find({ _id: { $in: userIds } });
  
  const userMap = new Map();
  users.forEach(user => userMap.set(user._id.toString(), user));
  
  return userIds.map(id => userMap.get(id));
});

// Use in resolver
Post: {
  author: async (parent: any, _: any, context: any) => {
    return context.loaders.userLoader.load(parent.author);
  }
}

Section 7: Testing Your GraphQL API

Using Apollo Studio / GraphQL Playground

1. Access GraphQL Playground

Navigate to http://localhost:4000/graphql

2. Add Authentication

Add header: Authorization: Bearer YOUR_TOKEN

3. Run Queries

Test all your queries and mutations with the interactive explorer

You're Now a GraphQL Expert! 🚀

You've learned schemas, resolvers, mutations, pagination, DataLoader, and best practices for scalable GraphQL APIs!

Back to Tutorial Overview