GraphQL API design. Covers schema, queries, mutations, and resolvers. Use when building or consuming GraphQL APIs. USE WHEN: user mentions "GraphQL", "schema definition", "resolvers", "mutations", "queries", "DataLoader", "N+1 problem", asks about "how to design GraphQL API", "GraphQL schema", "GraphQL authentication", "GraphQL pagination", "Apollo Server" DO NOT USE FOR: REST APIs - use `rest-api` instead; tRPC - use `trpc` instead; GraphQL code generation - use `graphql-codegen` instead

GitHub
安装命令
npx skhub add claude-dev-suite/graphql
Markdown
SKILL.md

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

DoDon't
Use input types for mutationsN+1 queries (use DataLoader)
Implement paginationReturn unbounded lists
Add field-level authExpose sensitive data
Use fragments for reuseOver-fetch data

Anti-Patterns

Anti-PatternWhy It's BadSolution
N+1 queriesCauses performance issues, database overloadUse DataLoader for batching
Exposing implementation details in schemaTight coupling, hard to refactorUse domain-driven schema design
No pagination on listsMemory issues, slow responsesImplement cursor or offset pagination
Allowing unbounded query depthDoS vulnerabilityAdd depth limiting
No query complexity limitsResource exhaustionAdd complexity analysis
Exposing sensitive fields without authSecurity vulnerabilityAdd field-level authorization
Using String for IDsType safety issuesUse ID! scalar type
Returning null instead of errorsPoor error handlingUse proper GraphQL error responses

Quick Troubleshooting

IssuePossible CauseSolution
Slow query performanceN+1 queriesImplement DataLoader, check resolver patterns
High memory usageLarge unbounded listsAdd pagination, limit query depth
"Cannot return null for non-nullable field"Missing data or resolver errorCheck database queries, add error handling
Query rejectedDepth or complexity limit exceededOptimize query, reduce nesting
Authentication errorsMissing or invalid tokenCheck context creation, verify token
Type mismatch errorsSchema/resolver mismatchEnsure resolver return types match schema
CORS errorsServer configuration issueConfigure CORS in Apollo Server
Introspection disabledProduction security settingEnable 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<string, Post[]>(async (userIds) => {
      const posts = await db.post.findMany({
        where: { authorId: { in: [...userIds] } },
      });
      const postsByUser = new Map<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

MetricAlert 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

SkillPurpose
GraphQL CodegenFull codegen setup
TanStack QueryData fetching hooks
React APIAlternative data patterns

Reference Documentation

发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

MIT

源路径

skills/api-design/graphql

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1