GraphQL logoGraphQLINTERMEDIATE

GraphQL

GraphQL cheat sheet with query syntax, mutations, subscriptions, schema types, resolvers, and API design patterns with code examples.

15 min read
graphqlapiqueriesmutationssubscriptionsapolloschemaresolvers

Sign in to mark items as known and track your progress.

Sign in

Setup & Basics

GraphQL Server Setup

📄 Codejavascript
// Install dependencies
npm install @apollo/server @as-integrations/express4 graphql graphql-tag express cors

// Basic GraphQL server with Apollo
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@as-integrations/express4');
const { gql } = require('graphql-tag');
const express = require('express');
const cors = require('cors');

// Type definitions (Schema)
const typeDefs = gql`
  type Query {
    hello: String
    users: [User!]!
    user(id: ID!): User
  }
  
  type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
  }
  
  type Post {
    id: ID!
    title: String!
    content: String!
    author: User!
  }
`;

// Resolvers
const resolvers = {
  Query: {
    hello: () => 'Hello World!',
    users: () => users,
    user: (parent, args) => users.find(u => u.id === args.id)
  },
  User: {
    posts: (parent) => posts.filter(p => p.authorId === parent.id)
  }
};

// Create server
async function startServer() {
  const app = express();
  const server = new ApolloServer({ typeDefs, resolvers });
  
  await server.start();
  app.use('/graphql', cors(), express.json(), expressMiddleware(server));
  
  app.listen(4000, () => {
    console.log('Server running at http://localhost:4000/graphql');
  });
}

startServer();
💡 GraphQL is a query language and runtime for APIs
⚡ Single endpoint typically at /graphql
📌 Strongly typed with schema definition
🟢 Works with any backend language or database

Schema Definition Language (SDL)

📄 Codegraphql
# Scalar types
scalar Date
scalar JSON

# Enums
enum Role {
  ADMIN
  USER
  GUEST
}

# Object types
type User {
  id: ID!                    # Non-nullable ID
  name: String!              # Required string
  email: String              # Optional string
  age: Int
  isActive: Boolean!
  role: Role!
  createdAt: Date!
  metadata: JSON
  posts: [Post!]!            # Non-null list of non-null Posts
  friends: [User]            # Nullable list of nullable Users
}

type Post {
  id: ID!
  title: String!
  content: String!
  published: Boolean!
  author: User!              # Relationship
  comments: [Comment!]!
  tags: [String!]!
}

# Input types for mutations
input CreateUserInput {
  name: String!
  email: String!
  password: String!
  role: Role = USER          # Default value
}

input UpdateUserInput {
  name: String
  email: String
  isActive: Boolean
}

# Interfaces
interface Node {
  id: ID!
}

# Union types
union SearchResult = User | Post | Comment

# Root types
type Query {
  # User queries
  users(limit: Int = 10, offset: Int = 0): [User!]!
  user(id: ID!): User
  currentUser: User
  
  # Post queries
  posts(published: Boolean): [Post!]!
  post(id: ID!): Post
  
  # Search
  search(query: String!): [SearchResult!]!
}

type Mutation {
  # User mutations
  createUser(input: CreateUserInput!): User!
  updateUser(id: ID!, input: UpdateUserInput!): User
  deleteUser(id: ID!): Boolean!
  
  # Post mutations
  createPost(title: String!, content: String!): Post!
  publishPost(id: ID!): Post
}

type Subscription {
  # Real-time updates
  userAdded: User!
  postPublished: Post!
  commentAdded(postId: ID!): Comment!
}
💡 Schema defines API structure and types
⚡ ! means non-nullable, [] means list
📌 Scalar types: Int, Float, String, Boolean, ID
🔥 Custom scalars for dates, JSON, etc.

Queries

Basic queries

graphql
# Simple query
query {
  hello
}

# Query with fields selection
query {
  users {
    id
    name
    email
  }
}

# Query with arguments
query {
  user(id: "123") {
    name
    email
    age
  }
}

# Nested queries
query {
  user(id: "123") {
    id
    name
    posts {
      id
      title
      comments {
        id
        content
        author {
          name
        }
      }
    }
  }
}
💡 Request exactly what you need
⚡ Single request for nested data
📌 No over-fetching or under-fetching
🟢 Predictable results matching query shape

Query variables and directives

graphql
# Query with variables
query GetPosts($limit: Int!, $offset: Int = 0, $published: Boolean) {
  posts(limit: $limit, offset: $offset, published: $published) {
    id
    title
    content
    author {
      name
    }
  }
}

# Variables object (sent separately)
{
  "limit": 10,
  "offset": 20,
  "published": true
}

# Directives
query GetUser($userId: ID!, $includeDetails: Boolean!, $skipPosts: Boolean!) {
  user(id: $userId) {
    id
    name
    
    # Include directive
    email @include(if: $includeDetails)
    phone @include(if: $includeDetails)
    address @include(if: $includeDetails)
    
    # Skip directive
    posts @skip(if: $skipPosts) {
      id
      title
    }
  }
}

# Custom directives
query {
  users {
    id
    name
    email @lowercase
    createdAt @formatDate(format: "MM/DD/YYYY")
    password @deprecated(reason: "Use auth service")
  }
}
💡 Variables make queries reusable
⚡ Directives modify query execution
📌 @include and @skip for conditional fields
🔥 Custom directives for advanced features

Mutations

Basic mutations

graphql
# Simple mutation
mutation {
  createUser(input: {
    name: "John Doe"
    email: "john@example.com"
    password: "secure123"
  }) {
    id
    name
    email
  }
}

# Mutation with variables
mutation CreatePost($title: String!, $content: String!) {
  createPost(title: $title, content: $content) {
    id
    title
    content
    published
    author {
      name
    }
  }
}

# Update mutation
mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
  updateUser(id: $id, input: $input) {
    id
    name
    email
    updatedAt
  }
}

# Delete mutation
mutation DeletePost($id: ID!) {
  deletePost(id: $id) {
    success
    message
  }
}
💡 Mutations modify server-side data
⚡ Return updated data after mutation
📌 Use input types for complex arguments
🟢 Can include queries in response

Optimistic updates and error handling

📄 Codejavascript
// Apollo Client mutation with optimistic response
import { gql, useMutation } from '@apollo/client';

const CREATE_POST = gql`
  mutation CreatePost($title: String!, $content: String!) {
    createPost(title: $title, content: $content) {
      id
      title
      content
      createdAt
      author {
        id
        name
      }
    }
  }
`;

function CreatePostForm() {
  const [createPost, { data, loading, error }] = useMutation(CREATE_POST, {
    // Optimistic response
    optimisticResponse: {
      createPost: {
        __typename: 'Post',
        id: 'temp-id',
        title: formData.title,
        content: formData.content,
        createdAt: new Date().toISOString(),
        author: {
          __typename: 'User',
          id: currentUser.id,
          name: currentUser.name
        }
      }
    },
    
    // Update cache after mutation
    update(cache, { data: { createPost } }) {
      cache.modify({
        fields: {
          posts(existingPosts = []) {
            const newPostRef = cache.writeFragment({
              data: createPost,
              fragment: gql`
                fragment NewPost on Post {
                  id
                  title
                  content
                  createdAt
                  author {
                    id
                    name
                  }
                }
              `
            });
            return [...existingPosts, newPostRef];
          }
        }
      });
    },
    
    // Error handling
    onError(error) {
      console.error('Mutation error:', error);
      toast.error('Failed to create post');
    },
    
    // Success handling
    onCompleted(data) {
      toast.success('Post created successfully');
      resetForm();
    }
  });
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    
    try {
      await createPost({
        variables: {
          title: formData.title,
          content: formData.content
        }
      });
    } catch (err) {
      // Error is handled by onError
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      {/* Form fields */}
      <button type="submit" disabled={loading}>
        {loading ? 'Creating...' : 'Create Post'}
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  );
}
💡 Optimistic UI updates for better UX
⚡ Rollback on error automatically
📌 Error handling with try-catch
🔥 Cache updates after mutations

Subscriptions

Real-time subscriptions

javascript
// Server-side subscription setup
const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();

// Type definitions
const typeDefs = gql`
  type Subscription {
    postAdded: Post!
    commentAdded(postId: ID!): Comment!
    userTyping(chatId: ID!): TypingEvent!
  }
  
  type TypingEvent {
    userId: ID!
    userName: String!
    isTyping: Boolean!
  }
`;

// Resolvers
const resolvers = {
  Mutation: {
    createPost: async (parent, args, context) => {
      const post = await db.post.create({ data: args });
      
      // Publish event
      pubsub.publish('POST_ADDED', { postAdded: post });
      
      return post;
    },
    
    addComment: async (parent, { postId, content }, context) => {
      const comment = await db.comment.create({
        data: { postId, content, userId: context.user.id }
      });
      
      // Publish to specific channel
      pubsub.publish(`COMMENT_ADDED_${postId}`, { 
        commentAdded: comment 
      });
      
      return comment;
    }
  },
  
  Subscription: {
    postAdded: {
      subscribe: () => pubsub.asyncIterator(['POST_ADDED'])
    },
    
    commentAdded: {
      subscribe: (parent, { postId }) => {
        return pubsub.asyncIterator([`COMMENT_ADDED_${postId}`]);
      }
    },
    
    userTyping: {
      subscribe: withFilter(
        () => pubsub.asyncIterator(['USER_TYPING']),
        (payload, variables) => {
          // Only send to users in the same chat
          return payload.chatId === variables.chatId;
        }
      )
    }
  }
};
💡 Real-time updates via WebSocket
⚡ Server pushes updates to clients
📌 Use for live features like chat
🔥 Efficient for selective updates

Resolvers

Resolver structure and context

javascript
// Resolver function signature
const resolvers = {
  Query: {
    // (parent, args, context, info) => result
    user: async (parent, args, context, info) => {
      // parent: Result from parent resolver
      // args: Arguments passed to field
      // context: Shared context (auth, db, etc.)
      // info: Query AST and execution info
      
      // Check authentication
      if (!context.user) {
        throw new Error('Not authenticated');
      }
      
      // Fetch from database
      return await context.db.user.findUnique({
        where: { id: args.id }
      });
    }
  }
};

// Context setup - context lives on the integration (Apollo Server 4+)
const server = new ApolloServer({ typeDefs, resolvers });

app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
  context: async ({ req }) => {
    // Get auth token
    const token = req.headers.authorization || '';
    
    // Verify user
    const user = await verifyToken(token);
    
    // Return context object
    return {
      user,
      db: prisma,
      dataSources: {
        userAPI: new UserAPI(),
        postAPI: new PostAPI()
      },
      req,
      pubsub
    };
  },
}));
💡 Resolvers fetch data for schema fields
⚡ Four arguments: parent, args, context, info
📌 Context shares data across resolvers
🟢 Can be sync or async functions

DataLoader and N+1 problem

📄 Codejavascript
// N+1 Problem Example (BAD)
const resolvers = {
  Query: {
    posts: async (parent, args, { db }) => {
      return await db.post.findMany(); // 1 query
    }
  },
  Post: {
    author: async (parent, args, { db }) => {
      // This runs once per post! (N queries)
      return await db.user.findUnique({
        where: { id: parent.authorId }
      });
    }
  }
};
// Result: 1 + N queries (if 100 posts, 101 queries!)

// Solution with DataLoader (GOOD)
const DataLoader = require('dataloader');

// Create loaders
function createLoaders(db) {
  return {
    user: new DataLoader(async (userIds) => {
      // Batch load all users in one query
      const users = await db.user.findMany({
        where: { id: { in: userIds } }
      });
      
      // Map back to original order
      const userMap = new Map();
      users.forEach(user => userMap.set(user.id, user));
      return userIds.map(id => userMap.get(id));
    }),
    
    postsByUser: new DataLoader(async (userIds) => {
      // Batch load all posts
      const posts = await db.post.findMany({
        where: { authorId: { in: userIds } }
      });
      
      // Group by user
      const postMap = new Map();
      userIds.forEach(id => postMap.set(id, []));
      posts.forEach(post => {
        const userPosts = postMap.get(post.authorId);
        userPosts.push(post);
      });
      
      return userIds.map(id => postMap.get(id));
    })
  };
}

// Use in context - loaders attach via the integration (Apollo Server 4+)
const server = new ApolloServer({ typeDefs, resolvers });

app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
  context: async ({ req }) => ({
    db,
    loaders: createLoaders(db) // New instance per request
  })
}));

// Use in resolvers
const resolvers = {
  Post: {
    author: async (parent, args, { loaders }) => {
      // Automatically batched!
      return await loaders.user.load(parent.authorId);
    }
  },
  User: {
    posts: async (parent, args, { loaders }) => {
      // Batched and cached
      return await loaders.postsByUser.load(parent.id);
    }
  }
};
// Result: Just 2 queries total!
💡 DataLoader batches and caches requests
⚡ Solves N+1 query problem
📌 Automatic request deduplication
🔥 Per-request caching

Authentication & Authorization

Authentication patterns

javascript
// Authentication setup
const jwt = require('jsonwebtoken');
const { GraphQLError } = require('graphql');

// Context with authentication
const server = new ApolloServer({ typeDefs, resolvers });

// Context moves to the integration function (Apollo Server 4+)
app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
  context: async ({ req }) => {
    // Get token from header
    const token = req.headers.authorization?.replace('Bearer ', '');
    
    let user = null;
    if (token) {
      try {
        // Verify token
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        
        // Get user from database
        user = await db.user.findUnique({
          where: { id: decoded.userId }
        });
      } catch (err) {
        // Invalid token, but don't throw here
        console.error('Invalid token:', err);
      }
    }
    
    return { user, db };
  },
}));

// Login mutation
const resolvers = {
  Mutation: {
    login: async (parent, { email, password }, { db }) => {
      // Find user
      const user = await db.user.findUnique({ where: { email } });
      
      if (!user) {
        throw new GraphQLError('Invalid credentials', { extensions: { code: 'UNAUTHENTICATED' } });
      }
      
      // Check password
      const validPassword = await bcrypt.compare(password, user.password);
      
      if (!validPassword) {
        throw new GraphQLError('Invalid credentials', { extensions: { code: 'UNAUTHENTICATED' } });
      }
      
      // Generate token
      const token = jwt.sign(
        { userId: user.id, email: user.email },
        process.env.JWT_SECRET,
        { expiresIn: '7d' }
      );
      
      return {
        token,
        user
      };
    },
    
    register: async (parent, { input }, { db }) => {
      // Check if user exists
      const existing = await db.user.findUnique({
        where: { email: input.email }
      });
      
      if (existing) {
        throw new Error('User already exists');
      }
      
      // Hash password
      const hashedPassword = await bcrypt.hash(input.password, 10);
      
      // Create user
      const user = await db.user.create({
        data: {
          ...input,
          password: hashedPassword
        }
      });
      
      // Generate token
      const token = jwt.sign(
        { userId: user.id },
        process.env.JWT_SECRET
      );
      
      return { token, user };
    }
  }
};
💡 Use context for auth state
⚡ JWT tokens common for GraphQL
📌 Check auth in resolvers or middleware
🔐 Never expose sensitive fields

Error Handling

Error types and handling

javascript
// Apollo Server error types
const { GraphQLError } = require('graphql');

// Custom error classes
class NotFoundError extends GraphQLError {
  constructor(message) {
    super(message, { extensions: { code: 'NOT_FOUND' } });
  }
}

class ConflictError extends GraphQLError {
  constructor(message) {
    super(message, { extensions: { code: 'CONFLICT' } });
  }
}

// Use in resolvers
const resolvers = {
  Query: {
    user: async (parent, { id }, { db }) => {
      const user = await db.user.findUnique({ where: { id } });
      
      if (!user) {
        throw new NotFoundError(`User with ID ${id} not found`);
      }
      
      return user;
    }
  },
  
  Mutation: {
    createUser: async (parent, { input }, { db }) => {
      // Validation
      if (!input.email.includes('@')) {
        throw new GraphQLError('Invalid email format', {
          extensions: { code: 'BAD_USER_INPUT', field: 'email', value: input.email }
        });
      }
      
      // Check uniqueness
      const existing = await db.user.findUnique({
        where: { email: input.email }
      });
      
      if (existing) {
        throw new ConflictError('Email already in use');
      }
      
      try {
        return await db.user.create({ data: input });
      } catch (error) {
        // Database error
        console.error('Database error:', error);
        throw new GraphQLError('Failed to create user', { extensions: { code: 'DATABASE_ERROR' } });
      }
    }
  }
};
💡 GraphQL returns 200 OK even with errors
⚡ Errors in errors array, data can be partial
📌 Use custom error classes for clarity
🔥 Format errors for production

Performance Optimization

Query optimization techniques

javascript
// Query depth limiting
const depthLimit = require('graphql-depth-limit');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(5) // Max depth of 5
  ]
});

// Query complexity analysis
const { 
  createComplexityLimitRule 
} = require('graphql-validation-complexity');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    createComplexityLimitRule(1000, {
      onCost: (cost) => console.log('Query cost:', cost),
      scalarCost: 1,
      objectCost: 2,
      listFactor: 10,
      introspectionCost: 1000,
    })
  ]
});

// Caching with Redis
const Redis = require('ioredis');
const redis = new Redis();

const resolvers = {
  Query: {
    popularPosts: async (parent, args, context) => {
      const cacheKey = 'popular_posts';
      
      // Check cache
      const cached = await redis.get(cacheKey);
      if (cached) {
        return JSON.parse(cached);
      }
      
      // Fetch from database
      const posts = await db.post.findMany({
        where: { published: true },
        orderBy: { views: 'desc' },
        take: 10
      });
      
      // Cache for 5 minutes
      await redis.setex(cacheKey, 300, JSON.stringify(posts));
      
      return posts;
    }
  }
};
💡 Use DataLoader for batching
⚡ Implement query depth limiting
📌 Add query complexity analysis
🔥 Cache with Redis or in-memory

Testing

Testing GraphQL APIs

javascript
// Unit testing resolvers
const { expect } = require('chai');
const sinon = require('sinon');

describe('User Resolvers', () => {
  let db;
  
  beforeEach(() => {
    // Mock database
    db = {
      user: {
        findUnique: sinon.stub(),
        findMany: sinon.stub(),
        create: sinon.stub(),
        update: sinon.stub()
      }
    };
  });
  
  describe('Query.user', () => {
    it('should return user by id', async () => {
      const mockUser = { id: '1', name: 'John' };
      db.user.findUnique.resolves(mockUser);
      
      const result = await resolvers.Query.user(
        null,
        { id: '1' },
        { db }
      );
      
      expect(result).to.deep.equal(mockUser);
      expect(db.user.findUnique.calledWith({
        where: { id: '1' }
      })).to.be.true;
    });
    
    it('should throw error if user not found', async () => {
      db.user.findUnique.resolves(null);
      
      try {
        await resolvers.Query.user(null, { id: '999' }, { db });
        expect.fail('Should have thrown');
      } catch (error) {
        expect(error.message).to.include('not found');
      }
    });
  });
});

// Integration testing with test server
const { ApolloServer } = require('@apollo/server');
const { gql } = require('graphql-tag');

describe('GraphQL Integration Tests', () => {
  let server;
  
  beforeEach(() => {
    server = new ApolloServer({ typeDefs, resolvers });
  });
  
  it('should fetch users', async () => {
    const GET_USERS = gql`
      query {
        users {
          id
          name
          email
        }
      }
    `;
    
    const res = await server.executeOperation(
      { query: GET_USERS },
      { contextValue: { db: mockDb, user: { id: '1', role: 'USER' } } }
    );

    // Results live under res.body.singleResult
    expect(res.body.singleResult.errors).to.be.undefined;
    expect(res.body.singleResult.data.users).to.be.an('array');
  });
  
  it('should create post', async () => {
    const CREATE_POST = gql`
      mutation CreatePost($title: String!, $content: String!) {
        createPost(title: $title, content: $content) {
          id
          title
          content
        }
      }
    `;
    
    const res = await server.executeOperation(
      {
        query: CREATE_POST,
        variables: {
          title: 'Test Post',
          content: 'Test content'
        }
      },
      { contextValue: { db: mockDb, user: { id: '1', role: 'USER' } } }
    );

    expect(res.body.singleResult.errors).to.be.undefined;
    expect(res.body.singleResult.data.createPost).to.include({
      title: 'Test Post',
      content: 'Test content'
    });
  });
});
💡 Test resolvers in isolation
⚡ Use test server for integration tests
📌 Mock data sources and context
🟢 Test both success and error cases

Best Practices

Schema design principles

graphql
# GOOD Schema Design
type User {
  id: ID!
  name: String!
  email: String!
  profile: UserProfile!
  posts(
    limit: Int = 10
    offset: Int = 0
    orderBy: PostOrderBy = CREATED_AT_DESC
  ): PostConnection!
}

# Use connections for pagination
type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

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

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

# Use input types for complex arguments
input CreatePostInput {
  title: String!
  content: String!
  tags: [String!]
  published: Boolean = false
}

# Use enums for fixed values
enum PostOrderBy {
  CREATED_AT_ASC
  CREATED_AT_DESC
  TITLE_ASC
  TITLE_DESC
  POPULARITY
}

# Deprecate instead of removing
type Post {
  id: ID!
  title: String!
  content: String!
  body: String! @deprecated(reason: "Use 'content' instead")
  author: User!
  tags: [String!]!
}

# BAD Schema Design (Avoid these)
type User {
  userId: String! # Use 'id: ID!' instead
  user_name: String! # Use camelCase: userName
  getPosts: [Post] # Don't use verbs, just 'posts'
  postsArray: [Post] # Redundant 'Array' suffix
}

# Too nested (BAD)
type User {
  posts: [Post!]!
    comments: [Comment!]!
      replies: [Reply!]!
        reactions: [Reaction!]! # Too deep!
}
💡 Design schema for clients, not database
⚡ Use clear, consistent naming
📌 Avoid deep nesting (max 3-4 levels)
🎯 Version via field deprecation, not versions

Security best practices

📄 Codejavascript
// Security configuration
const { ApolloServer } = require('@apollo/server');
const { expressMiddleware } = require('@as-integrations/express4');
const { GraphQLError } = require('graphql');
const rateLimit = require('express-rate-limit');
const depthLimit = require('graphql-depth-limit');
const costAnalysis = require('graphql-cost-analysis');

// Rate limiting
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Max 100 requests
  message: 'Too many requests'
});

app.use('/graphql', limiter);

// Apollo Server with security
const server = new ApolloServer({
  typeDefs,
  resolvers,
  
  // Disable introspection in production
  introspection: process.env.NODE_ENV !== 'production',

  // Validation rules
  validationRules: [
    // Limit query depth
    depthLimit(7),
    
    // Limit query complexity
    costAnalysis({
      maximumCost: 1000,
      defaultCost: 1,
      variables: {},
      scalarCost: 1,
      objectCost: 2,
      listFactor: 10,
      introspectionCost: 1000,
      enforceIntrospectionCost: true,
      onComplete: (cost) => {
        console.log('Query cost:', cost);
      }
    })
  ],
  
  // Format errors (hide sensitive info)
  formatError: (err) => {
    // Log full error server-side
    console.error(err);
    
    // Don't leak internal errors
    if (err.message.includes('Database')) {
      return new Error('Internal server error');
    }
    
    return err;
  },
});

// Context moves to the integration (Apollo Server 4+)
app.use('/graphql', cors(), express.json(), expressMiddleware(server, {
  context: async ({ req }) => {
    // CSRF protection
    const csrfToken = req.headers['x-csrf-token'];
    if (req.method === 'POST' && !csrfToken) {
      throw new Error('CSRF token missing');
    }
    
    // Rate limiting per user
    const ip = req.ip;
    const userLimiter = getUserRateLimiter(ip);
    
    if (!userLimiter.check()) {
      throw new Error('Rate limit exceeded');
    }
    
    return { req };
  },
}));

// Input validation
const validator = require('validator');

const resolvers = {
  Mutation: {
    createUser: async (parent, { input }) => {
      // Validate email
      if (!validator.isEmail(input.email)) {
        throw new GraphQLError('Invalid email', { extensions: { code: 'BAD_USER_INPUT' } });
      }
      
      // Validate and sanitize
      const sanitized = {
        name: validator.escape(input.name),
        email: validator.normalizeEmail(input.email),
        bio: validator.escape(input.bio || '')
      };
      
      // Check for SQL injection attempts
      if (containsSQLInjection(input)) {
        throw new Error('Invalid input');
      }
      
      return createUser(sanitized);
    }
  }
};

// Timeout protection
const server = new ApolloServer({
  typeDefs,
  resolvers,
  plugins: [
    {
      requestDidStart() {
        return {
          willSendResponse(requestContext) {
            // Timeout after 30 seconds
            setTimeout(() => {
              if (!requestContext.response.http.body) {
                throw new Error('Request timeout');
              }
            }, 30000);
          }
        };
      }
    }
  ]
});
🔐 Always validate and sanitize inputs
⚡ Implement rate limiting
📌 Disable introspection in production
🔥 Use query depth and complexity limits