🔷 GraphQL Course

🎯 Learning Objectives

After completing this course, you will be able to:

⚡ 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

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

📝 Exercise 1: Social Media API (4 points)

Build a GraphQL API for a social media platform:

📝 Exercise 2: Optimize Performance (3 points)

Optimize your GraphQL API:

📝 Exercise 3: Advanced Features (3 points)

Implement advanced GraphQL features:

🎉 Summary

You've completed the GraphQL course! You now know:

Total points available: 10/10

⬅️ Previous

Course 7: REST APIs