🎯 Learning Objectives
After completing this course, you will be able to:
- Understand GraphQL fundamentals and advantages
- Define schemas and types
- Write queries and mutations
- Implement resolvers
- Handle subscriptions for real-time data
- Optimize GraphQL performance
- Build a complete GraphQL API
⚡ Prerequisites
- REST APIs Course completed
- Strong JavaScript/Node.js knowledge
- Understanding of databases
- Basic TypeScript knowledge (helpful)
1. Introduction to GraphQL
What is GraphQL?
GraphQL is a query language for APIs and a runtime for executing those queries. It provides a complete and understandable description of the data in your API.
GraphQL vs REST
- Single Endpoint: One endpoint for all operations
- Client-Specified Queries: Request exactly what you need
- No Over/Under-fetching: Get precise data
- Strong Typing: Schema-based type system
- Real-time: Built-in subscriptions
- Introspection: Self-documenting API
Core Concepts
- Schema: Defines API structure
- Types: Data structure definitions
- Queries: Read operations
- Mutations: Write operations
- Resolvers: Functions that fetch data
- Subscriptions: Real-time updates
GraphQL Setup
// Install dependencies
npm install apollo-server graphql
// server.js
const { ApolloServer, gql } = require('apollo-server');
// Define schema
const typeDefs = gql`
type Query {
hello: String
}
`;
// Define resolvers
const resolvers = {
Query: {
hello: () => 'Hello, GraphQL!'
}
};
// Create server
const server = new ApolloServer({
typeDefs,
resolvers
});
// Start server
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
2. Schema & Types
Type Definitions
const typeDefs = gql`
# Scalar Types
# String, Int, Float, Boolean, ID
# Object Type
type User {
id: ID! # ! means required
name: String!
email: String!
age: Int
posts: [Post!]! # Array of Posts
createdAt: String!
}
type Post {
id: ID!
title: String!
content: String!
published: Boolean!
author: User!
comments: [Comment!]!
createdAt: String!
}
type Comment {
id: ID!
text: String!
author: User!
post: Post!
createdAt: String!
}
# Input Type (for mutations)
input CreateUserInput {
name: String!
email: String!
age: Int
}
input UpdateUserInput {
name: String
email: String
age: Int
}
# Enum Type
enum Role {
ADMIN
USER
EDITOR
}
# Interface
interface Node {
id: ID!
createdAt: String!
}
# Union Type
union SearchResult = User | Post | Comment
# Query Type
type Query {
user(id: ID!): User
users: [User!]!
post(id: ID!): Post
posts: [Post!]!
search(query: String!): [SearchResult!]!
}
# Mutation Type
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(title: String!, content: String!, authorId: ID!): Post!
}
# Subscription Type
type Subscription {
userCreated: User!
postCreated: Post!
}
`;
3. Queries
Query Examples
# Basic Query
query {
users {
id
name
email
}
}
# Query with Arguments
query {
user(id: "123") {
id
name
email
posts {
id
title
}
}
}
# Named Query with Variables
query GetUser($userId: ID!) {
user(id: $userId) {
id
name
email
posts {
id
title
comments {
id
text
author {
name
}
}
}
}
}
# Variables (sent separately)
{
"userId": "123"
}
# Query with Fragments
query {
user(id: "123") {
...UserFields
posts {
...PostFields
}
}
}
fragment UserFields on User {
id
name
email
createdAt
}
fragment PostFields on Post {
id
title
content
published
}
# Query with Aliases
query {
user1: user(id: "123") {
name
}
user2: user(id: "456") {
name
}
}
# Query with Directives
query GetUser($withPosts: Boolean!) {
user(id: "123") {
name
email
posts @include(if: $withPosts) {
title
}
}
}
4. Mutations
Mutation Examples
# Create User
mutation {
createUser(input: {
name: "John Doe"
email: "john@example.com"
age: 30
}) {
id
name
email
createdAt
}
}
# Update User
mutation {
updateUser(
id: "123"
input: {
name: "Jane Doe"
email: "jane@example.com"
}
) {
id
name
email
}
}
# Delete User
mutation {
deleteUser(id: "123")
}
# Multiple Mutations
mutation {
createUser1: createUser(input: {
name: "User 1"
email: "user1@example.com"
}) {
id
name
}
createUser2: createUser(input: {
name: "User 2"
email: "user2@example.com"
}) {
id
name
}
}
# Mutation with Variables
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
email
}
}
# Variables
{
"input": {
"name": "John Doe",
"email": "john@example.com",
"age": 30
}
}
5. Resolvers
Resolver Implementation
const resolvers = {
// Query Resolvers
Query: {
// Get all users
users: async () => {
return await User.find();
},
// Get user by ID
user: async (parent, { id }) => {
return await User.findById(id);
},
// Get all posts
posts: async () => {
return await Post.find();
},
// Get post by ID
post: async (parent, { id }) => {
return await Post.findById(id);
},
// Search
search: async (parent, { query }) => {
const users = await User.find({ name: new RegExp(query, 'i') });
const posts = await Post.find({ title: new RegExp(query, 'i') });
return [...users, ...posts];
}
},
// Mutation Resolvers
Mutation: {
// Create user
createUser: async (parent, { input }) => {
const user = new User(input);
await user.save();
return user;
},
// Update user
updateUser: async (parent, { id, input }) => {
const user = await User.findByIdAndUpdate(
id,
input,
{ new: true, runValidators: true }
);
if (!user) {
throw new Error('User not found');
}
return user;
},
// Delete user
deleteUser: async (parent, { id }) => {
const user = await User.findByIdAndDelete(id);
return !!user;
},
// Create post
createPost: async (parent, { title, content, authorId }) => {
const post = new Post({
title,
content,
author: authorId,
published: false
});
await post.save();
return post;
}
},
// Field Resolvers
User: {
// Resolve user's posts
posts: async (parent) => {
return await Post.find({ author: parent.id });
}
},
Post: {
// Resolve post's author
author: async (parent) => {
return await User.findById(parent.author);
},
// Resolve post's comments
comments: async (parent) => {
return await Comment.find({ post: parent.id });
}
},
Comment: {
// Resolve comment's author
author: async (parent) => {
return await User.findById(parent.author);
},
// Resolve comment's post
post: async (parent) => {
return await Post.findById(parent.post);
}
},
// Union Type Resolver
SearchResult: {
__resolveType(obj) {
if (obj.email) return 'User';
if (obj.title) return 'Post';
if (obj.text) return 'Comment';
return null;
}
}
};
Context and Authentication
const { ApolloServer } = require('apollo-server');
const jwt = require('jsonwebtoken');
// Create server with context
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
// Get token from headers
const token = req.headers.authorization || '';
// Verify token
try {
const user = jwt.verify(token.replace('Bearer ', ''), process.env.JWT_SECRET);
return { user };
} catch (error) {
return {};
}
}
});
// Protected resolver
const resolvers = {
Query: {
me: (parent, args, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
return User.findById(context.user.id);
}
},
Mutation: {
createPost: async (parent, { title, content }, context) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const post = new Post({
title,
content,
author: context.user.id
});
await post.save();
return post;
}
}
};
6. Subscriptions
Real-time Subscriptions
const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();
const typeDefs = gql`
type Subscription {
userCreated: User!
postCreated: Post!
commentAdded(postId: ID!): Comment!
}
`;
const resolvers = {
Mutation: {
createUser: async (parent, { input }) => {
const user = new User(input);
await user.save();
// Publish event
pubsub.publish('USER_CREATED', { userCreated: user });
return user;
},
createPost: async (parent, { title, content, authorId }) => {
const post = new Post({ title, content, author: authorId });
await post.save();
// Publish event
pubsub.publish('POST_CREATED', { postCreated: post });
return post;
},
addComment: async (parent, { postId, text, authorId }) => {
const comment = new Comment({ post: postId, text, author: authorId });
await comment.save();
// Publish event with filter
pubsub.publish('COMMENT_ADDED', {
commentAdded: comment,
postId
});
return comment;
}
},
Subscription: {
userCreated: {
subscribe: () => pubsub.asyncIterator(['USER_CREATED'])
},
postCreated: {
subscribe: () => pubsub.asyncIterator(['POST_CREATED'])
},
commentAdded: {
subscribe: (parent, { postId }) => {
return pubsub.asyncIterator(['COMMENT_ADDED']);
},
resolve: (payload, { postId }) => {
// Filter by postId
if (payload.postId === postId) {
return payload.commentAdded;
}
return null;
}
}
}
};
// Client-side subscription
subscription {
userCreated {
id
name
email
}
}
subscription {
commentAdded(postId: "123") {
id
text
author {
name
}
}
}
7. Best Practices
💡 GraphQL Best Practices
- Schema Design: Design from client perspective
- Pagination: Implement cursor-based pagination
- Error Handling: Use custom error types
- N+1 Problem: Use DataLoader for batching
- Depth Limiting: Prevent deeply nested queries
- Query Complexity: Limit expensive queries
- Caching: Implement proper caching strategies
- Documentation: Add descriptions to schema
DataLoader for N+1 Problem
const DataLoader = require('dataloader');
// Create DataLoader
const userLoader = new DataLoader(async (userIds) => {
const users = await User.find({ _id: { $in: userIds } });
return userIds.map(id => users.find(user => user.id === id));
});
// Use in resolver
const resolvers = {
Post: {
author: async (parent, args, context) => {
return context.userLoader.load(parent.author);
}
}
};
// Add to context
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => ({
userLoader: new DataLoader(batchUsers)
})
});
Practice Exercises
Build a GraphQL API for a social media platform:
- User profiles with posts and followers
- Create, read, update, delete operations
- Real-time notifications with subscriptions
- Implement authentication and authorization
Optimize your GraphQL API:
- Implement DataLoader for batching
- Add query depth limiting
- Implement query complexity analysis
- Add caching with Redis
Implement advanced GraphQL features:
- Cursor-based pagination
- File upload handling
- Custom directives
- Schema stitching or federation
🎉 Summary
You've completed the GraphQL course! You now know:
- GraphQL fundamentals and advantages over REST
- Schema definition and type system
- Writing queries and mutations
- Implementing resolvers and context
- Real-time subscriptions
- Performance optimization techniques
- Best practices for GraphQL APIs
Total points available: 10/10