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 --initSection 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