Claude
Skills
Sign in
Back

graphql-performance

Included with Lifetime
$97 forever

Use when optimizing GraphQL API performance with query complexity analysis, batching, caching strategies, depth limiting, monitoring, and database optimization.

Backend & APIs

What this skill does


# GraphQL Performance

Apply GraphQL performance optimization techniques to create efficient,
scalable APIs. This skill covers query complexity analysis, depth
limiting, batching and caching strategies, DataLoader optimization,
monitoring, tracing, and database query optimization.

## Query Complexity Analysis

Query complexity analysis prevents expensive queries from overwhelming
your server by calculating and limiting the computational cost.

```typescript
import { GraphQLError } from 'graphql';
import { ApolloServer } from '@apollo/server';

// Complexity calculator
const getComplexity = (field, childComplexity, args) => {
  // Base complexity for field
  let complexity = 1;

  // List multiplier based on limit argument
  if (args.limit) {
    complexity = args.limit;
  } else if (args.first) {
    complexity = args.first;
  }

  // Add child complexity
  return complexity + childComplexity;
};

// Directive-based complexity
const schema = `
  directive @complexity(
    value: Int!
    multipliers: [String!]
  ) on FIELD_DEFINITION

  type Query {
    user(id: ID!): User @complexity(value: 1)
    users(limit: Int): [User!]! @complexity(
      value: 1,
      multipliers: ["limit"]
    )
    posts(first: Int): [Post!]! @complexity(
      value: 5,
      multipliers: ["first"]
    )
  }

  type User {
    id: ID!
    posts: [Post!]! @complexity(value: 10)
  }
`;

// Complexity validation plugin
const complexityPlugin = {
  requestDidStart: () => ({
    async didResolveOperation({ request, document, operationName }) {
      const complexity = calculateComplexity({
        document,
        operationName,
        variables: request.variables
      });

      const maxComplexity = 1000;

      if (complexity > maxComplexity) {
        throw new GraphQLError(
          `Query is too complex: ${complexity}. ` +
          `Maximum allowed: ${maxComplexity}`,
          {
            extensions: {
              code: 'QUERY_TOO_COMPLEX',
              complexity,
              maxComplexity
            }
          }
        );
      }
    }
  })
};

// Manual complexity calculation
const calculateComplexity = ({ document, operationName, variables }) => {
  let totalComplexity = 0;

  const visit = (node, multiplier = 1) => {
    if (node.kind === 'Field') {
      // Get field complexity from directive or default
      const complexity = getFieldComplexity(node);

      // Handle multipliers from arguments
      const args = getArguments(node, variables);
      const fieldMultiplier = getMultiplier(args);

      totalComplexity += complexity * multiplier * fieldMultiplier;

      // Visit child fields
      if (node.selectionSet) {
        node.selectionSet.selections.forEach(child =>
          visit(child, multiplier * fieldMultiplier)
        );
      }
    }
  };

  visit(document);
  return totalComplexity;
};
```

## Depth Limiting

Prevent deeply nested queries that can cause performance issues and
potential denial of service attacks.

```typescript
import { ValidationContext, GraphQLError } from 'graphql';

const depthLimit = (maxDepth: number) => {
  return (validationContext: ValidationContext) => {
    return {
      Field(node, key, parent, path, ancestors) {
        const depth = ancestors.filter(
          ancestor => ancestor.kind === 'Field'
        ).length;

        if (depth > maxDepth) {
          validationContext.reportError(
            new GraphQLError(
              `Query exceeds maximum depth of ${maxDepth}. ` +
              `Found depth of ${depth}.`,
              {
                nodes: [node],
                extensions: {
                  code: 'DEPTH_LIMIT_EXCEEDED',
                  depth,
                  maxDepth
                }
              }
            )
          );
        }
      }
    };
  };
};

// Usage with Apollo Server
const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [depthLimit(7)]
});

// Example queries
// ✅ Allowed (depth: 4)
query {
  user {
    posts {
      comments {
        author {
          username
        }
      }
    }
  }
}

// ❌ Rejected (depth: 8)
query {
  user {
    friends {
      friends {
        friends {
          friends {
            friends {
              friends {
                friends {
                  username
                }
              }
            }
          }
        }
      }
    }
  }
}
```

## Query Cost Analysis

Implement cost-based rate limiting to protect against expensive
queries.

```typescript
interface CostConfig {
  objectCost: number;
  scalarCost: number;
  defaultListSize: number;
}

const calculateQueryCost = (
  document,
  variables,
  config: CostConfig
) => {
  let totalCost = 0;

  const visit = (node, multiplier = 1) => {
    if (node.kind === 'Field') {
      const fieldType = getFieldType(node);

      // List cost
      if (isListType(fieldType)) {
        const listSize = getListSize(node, variables) ||
          config.defaultListSize;
        multiplier *= listSize;
      }

      // Field cost
      if (isObjectType(fieldType)) {
        totalCost += config.objectCost * multiplier;
      } else {
        totalCost += config.scalarCost * multiplier;
      }

      // Visit children
      if (node.selectionSet) {
        node.selectionSet.selections.forEach(child =>
          visit(child, multiplier)
        );
      }
    }
  };

  visit(document);
  return totalCost;
};

// Rate limiting based on cost
const costLimitPlugin = {
  requestDidStart: () => ({
    async didResolveOperation({ request, document, contextValue }) {
      const cost = calculateQueryCost(
        document,
        request.variables,
        { objectCost: 1, scalarCost: 0.1, defaultListSize: 10 }
      );

      // Check user's rate limit
      const limit = await getRateLimit(contextValue.user);
      const used = await getCostUsed(contextValue.user);

      if (used + cost > limit) {
        throw new GraphQLError('Rate limit exceeded', {
          extensions: {
            code: 'RATE_LIMIT_EXCEEDED',
            cost,
            used,
            limit
          }
        });
      }

      // Track cost usage
      await incrementCostUsed(contextValue.user, cost);
    }
  })
};
```

## Batching with DataLoader

Optimize data fetching by batching multiple requests into single
database queries.

```typescript
import DataLoader from 'dataloader';

// Basic DataLoader setup
const createUserLoader = (db) => {
  return new DataLoader<string, User>(
    async (userIds) => {
      // Single query for all users
      const users = await db.users.findByIds(userIds);

      // Map to maintain order
      const userMap = new Map(users.map(u => [u.id, u]));
      return userIds.map(id => userMap.get(id) || null);
    },
    {
      // Cache for duration of request
      cache: true,
      // Batch at most 100 at a time
      maxBatchSize: 100,
      // Wait 10ms before batching
      batchScheduleFn: callback => setTimeout(callback, 10)
    }
  );
};

// Advanced batching with joins
const createPostsLoader = (db) => {
  return new DataLoader<string, Post[]>(
    async (authorIds) => {
      // Single query with all author IDs
      const posts = await db.posts.query()
        .whereIn('authorId', authorIds)
        .select();

      // Group by author ID
      const postsByAuthor = authorIds.map(authorId =>
        posts.filter(post => post.authorId === authorId)
      );

      return postsByAuthor;
    }
  );
};

// Multi-key loader
interface PostKey {
  authorId: string;
  status: string;
}

const createFilteredPostsLoader = (db) => {
  return new DataLoader<PostKey, Post[]>(
    async (keys) => {
      // Extract unique author IDs and statuses
      const authorIds = [...new Set(keys.map(k => k.authorId))];
      const statuses = [...new Set(keys.map(k => k.status))];

      // Single query for all combinations
      const posts = await db.posts.query()
        .whereIn('authorId', authorIds)
        .whereIn('status', statuses)
  

Related in Backend & APIs