graphql

GraphQL Core Knowledge

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "graphql" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-graphql

GraphQL Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: graphql for comprehensive documentation.

Schema Definition

type User { id: ID! name: String! email: String! posts: [Post!]! createdAt: DateTime! }

type Post { id: ID! title: String! content: String author: User! published: Boolean! }

type Query { user(id: ID!): User users(limit: Int, offset: Int): [User!]! post(id: ID!): Post }

type Mutation { createUser(input: CreateUserInput!): User! updateUser(id: ID!, input: UpdateUserInput!): User! deleteUser(id: ID!): Boolean! }

input CreateUserInput { name: String! email: String! }

Resolvers

const resolvers = { Query: { user: (, { id }, context) => { return context.db.users.findUnique({ where: { id } }); }, users: (, { limit, offset }, context) => { return context.db.users.findMany({ take: limit, skip: offset }); }, }, Mutation: { createUser: (_, { input }, context) => { return context.db.users.create({ data: input }); }, }, User: { posts: (parent, _, context) => { return context.db.posts.findMany({ where: { authorId: parent.id } }); }, }, };

Queries

query GetUser($id: ID!) { user(id: $id) { id name email posts { title published } } }

mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name } }

When NOT to Use This Skill

  • REST API design (use rest-api skill)

  • OpenAPI/Swagger documentation (use openapi skill)

  • tRPC type-safe APIs (use trpc skill)

  • Generating GraphQL types from schema (use graphql-codegen skill)

  • Simple CRUD operations where REST is sufficient

Best Practices

Do Don't

Use input types for mutations N+1 queries (use DataLoader)

Implement pagination Return unbounded lists

Add field-level auth Expose sensitive data

Use fragments for reuse Over-fetch data

Anti-Patterns

Anti-Pattern Why It's Bad Solution

N+1 queries Causes performance issues, database overload Use DataLoader for batching

Exposing implementation details in schema Tight coupling, hard to refactor Use domain-driven schema design

No pagination on lists Memory issues, slow responses Implement cursor or offset pagination

Allowing unbounded query depth DoS vulnerability Add depth limiting

No query complexity limits Resource exhaustion Add complexity analysis

Exposing sensitive fields without auth Security vulnerability Add field-level authorization

Using String for IDs Type safety issues Use ID! scalar type

Returning null instead of errors Poor error handling Use proper GraphQL error responses

Quick Troubleshooting

Issue Possible Cause Solution

Slow query performance N+1 queries Implement DataLoader, check resolver patterns

High memory usage Large unbounded lists Add pagination, limit query depth

"Cannot return null for non-nullable field" Missing data or resolver error Check database queries, add error handling

Query rejected Depth or complexity limit exceeded Optimize query, reduce nesting

Authentication errors Missing or invalid token Check context creation, verify token

Type mismatch errors Schema/resolver mismatch Ensure resolver return types match schema

CORS errors Server configuration issue Configure CORS in Apollo Server

Introspection disabled Production security setting Enable for development, disable in production

Production Readiness

Security Configuration

// Query depth limiting import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({ schema, validationRules: [depthLimit(10)], // Max 10 levels deep });

// Query complexity limiting import { createComplexityLimitRule } from 'graphql-validation-complexity';

const complexityLimitRule = createComplexityLimitRule(1000, { scalarCost: 1, objectCost: 10, listFactor: 10, });

// Disable introspection in production const server = new ApolloServer({ introspection: process.env.NODE_ENV !== 'production', plugins: [ process.env.NODE_ENV === 'production' ? ApolloServerPluginLandingPageDisabled() : ApolloServerPluginLandingPageLocalDefault(), ], });

N+1 Query Prevention (DataLoader)

import DataLoader from 'dataloader';

// Create loader per request (in context) function createLoaders(db: PrismaClient) { return { userLoader: new DataLoader<string, User>(async (ids) => { const users = await db.user.findMany({ where: { id: { in: [...ids] } }, }); const userMap = new Map(users.map(u => [u.id, u])); return ids.map(id => userMap.get(id) || null); }),

postsByUserLoader: new DataLoader&#x3C;string, Post[]>(async (userIds) => {
  const posts = await db.post.findMany({
    where: { authorId: { in: [...userIds] } },
  });
  const postsByUser = new Map&#x3C;string, Post[]>();
  posts.forEach(p => {
    const existing = postsByUser.get(p.authorId) || [];
    postsByUser.set(p.authorId, [...existing, p]);
  });
  return userIds.map(id => postsByUser.get(id) || []);
}),

}; }

// Use in resolvers const resolvers = { User: { posts: (parent, _, context) => { return context.loaders.postsByUserLoader.load(parent.id); }, }, };

Field-Level Authorization

import { rule, shield, and, or } from 'graphql-shield';

const isAuthenticated = rule()((parent, args, context) => { return context.user !== null; });

const isAdmin = rule()((parent, args, context) => { return context.user?.role === 'ADMIN'; });

const isOwner = rule()((parent, args, context) => { return parent.authorId === context.user?.id; });

const permissions = shield({ Query: { users: isAuthenticated, user: isAuthenticated, }, Mutation: { deleteUser: and(isAuthenticated, or(isAdmin, isOwner)), updateUser: and(isAuthenticated, or(isAdmin, isOwner)), }, User: { email: or(isAdmin, isOwner), // Only owner or admin can see email }, });

const server = new ApolloServer({ schema: applyMiddleware(schema, permissions), });

Rate Limiting

import { rateLimitDirective } from 'graphql-rate-limit-directive';

const { rateLimitDirectiveTypeDefs, rateLimitDirectiveTransformer } = rateLimitDirective();

const typeDefs = gql` ${rateLimitDirectiveTypeDefs}

type Query { users: [User!]! @rateLimit(limit: 100, duration: 60) }

type Mutation { createUser(input: CreateUserInput!): User! @rateLimit(limit: 10, duration: 60) } `;

Error Handling

// Custom error formatting const server = new ApolloServer({ formatError: (formattedError, error) => { // Log original error logger.error(error);

// Don't leak internal errors
if (formattedError.extensions?.code === 'INTERNAL_SERVER_ERROR') {
  return {
    message: 'Internal server error',
    extensions: {
      code: 'INTERNAL_SERVER_ERROR',
    },
  };
}

// Remove stack trace in production
if (process.env.NODE_ENV === 'production') {
  delete formattedError.extensions?.stacktrace;
}

return formattedError;

}, });

Monitoring Metrics

Metric Alert Threshold

Query duration p99

500ms

Error rate

1%

Complexity score (avg)

500

Depth exceeded errors

10/min

DataLoader cache hit ratio < 50%

Pagination (Relay-style)

type Query { users(first: Int, after: String, last: Int, before: String): UserConnection! }

type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! }

type UserEdge { cursor: String! node: User! }

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

Request Logging

const server = new ApolloServer({ plugins: [ { async requestDidStart(requestContext) { const start = Date.now();

    return {
      async willSendResponse(ctx) {
        logger.info({
          operationName: ctx.request.operationName,
          query: ctx.request.query,
          variables: ctx.request.variables,
          duration: Date.now() - start,
          errors: ctx.errors?.length || 0,
        });
      },
    };
  },
},

], });

Checklist

  • Query depth limiting

  • Query complexity limiting

  • Introspection disabled in production

  • DataLoader for N+1 prevention

  • Field-level authorization

  • Rate limiting on mutations

  • Custom error formatting

  • Relay-style pagination

  • Request logging with timing

  • Input validation

  • Persisted queries (optional)

  • APQ (Automatic Persisted Queries) enabled

Code Generation

GraphQL Codegen generates TypeScript types and hooks from your GraphQL schema and operations.

Quick Setup

npm install -D @graphql-codegen/cli @graphql-codegen/client-preset

// codegen.ts import { CodegenConfig } from '@graphql-codegen/cli';

const config: CodegenConfig = { schema: 'http://localhost:4000/graphql', documents: ['src//*.graphql', 'src//*.tsx'], generates: { './src/gql/': { preset: 'client', plugins: [], }, }, };

export default config;

Generated Usage

import { graphql } from '@/gql'; import { useQuery } from '@tanstack/react-query';

const UserQuery = graphql( query GetUser($id: ID!) { user(id: $id) { id name email } });

function UserProfile({ id }: { id: string }) { const { data } = useQuery({ queryKey: ['user', id], queryFn: () => request(endpoint, UserQuery, { id }), });

return <div>{data?.user?.name}</div>; }

Related Skills

Skill Purpose

GraphQL Codegen Full codegen setup

TanStack Query Data fetching hooks

React API Alternative data patterns

Reference Documentation

  • DataLoader

  • Authentication

  • Code Generation

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

graphql

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review