graphql

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

GraphQL Core Knowledge

GraphQL核心知识

Deep Knowledge: Use
mcp__documentation__fetch_docs
with technology:
graphql
for comprehensive documentation.
深度知识: 使用技术参数为
graphql
mcp__documentation__fetch_docs
工具获取完整文档。

Schema Definition

Schema定义

graphql
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!
}
graphql
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

Resolvers

typescript
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 } });
    },
  },
};
typescript
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

Queries

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

mutation CreateUser($input: CreateUserInput!) {
  createUser(input: $input) {
    id
    name
  }
}
graphql
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
  • REST API设计(使用
    rest-api
    技能)
  • OpenAPI/Swagger文档(使用
    openapi
    技能)
  • tRPC类型安全API(使用
    trpc
    技能)
  • 从schema生成GraphQL类型定义(使用
    graphql-codegen
    技能)
  • REST足以满足需求的简单CRUD操作

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
推荐做法不推荐做法
为mutations使用input类型N+1查询(请使用DataLoader)
实现分页能力返回无边界列表
添加字段级鉴权暴露敏感数据
使用fragments实现复用过度拉取数据

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 IDs
Type safety issuesUse
ID!
scalar type
Returning null instead of errorsPoor error handlingUse proper GraphQL error responses
反模式弊端解决方案
N+1查询导致性能问题、数据库过载使用DataLoader做批量请求
在schema中暴露实现细节耦合度高、难以重构使用领域驱动的schema设计
列表没有分页能力内存问题、响应缓慢实现游标分页或偏移量分页
允许无边界的查询深度存在DoS攻击风险添加查询深度限制
没有查询复杂度限制资源耗尽添加复杂度分析校验
未做鉴权就暴露敏感字段安全漏洞添加字段级权限控制
使用
String
作为ID类型
类型安全问题使用
ID!
标量类型
返回null而非错误信息错误处理能力差使用规范的GraphQL错误响应

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
问题可能原因解决方案
查询性能缓慢N+1查询实现DataLoader、检查resolver逻辑
内存占用过高大型无边界列表添加分页、限制查询深度
"Cannot return null for non-nullable field"数据缺失或resolver报错检查数据库查询、添加错误处理
查询被拒绝超出深度或复杂度限制优化查询、减少嵌套层级
鉴权错误令牌缺失或无效检查上下文生成逻辑、验证令牌
类型不匹配错误Schema和resolver不匹配确保resolver返回类型和schema定义一致
CORS错误服务端配置问题在Apollo Server中配置CORS
自省功能被禁用生产环境安全配置开发环境开启、生产环境关闭

Production Readiness

生产就绪配置

Security Configuration

安全配置

typescript
// 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(),
  ],
});
typescript
// 查询深度限制
import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({
  schema,
  validationRules: [depthLimit(10)], // 最大嵌套层级为10
});

// 查询复杂度限制
import { createComplexityLimitRule } from 'graphql-validation-complexity';

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

// 生产环境关闭自省功能
const server = new ApolloServer({
  introspection: process.env.NODE_ENV !== 'production',
  plugins: [
    process.env.NODE_ENV === 'production'
      ? ApolloServerPluginLandingPageDisabled()
      : ApolloServerPluginLandingPageLocalDefault(),
  ],
});

N+1 Query Prevention (DataLoader)

N+1查询防范(DataLoader)

typescript
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);
    },
  },
};
typescript
import DataLoader from 'dataloader';

// 每个请求创建loader(在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) || []);
    }),
  };
}

// 在resolver中使用
const resolvers = {
  User: {
    posts: (parent, _, context) => {
      return context.loaders.postsByUserLoader.load(parent.id);
    },
  },
};

Field-Level Authorization

字段级权限控制

typescript
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),
});
typescript
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), // 仅所有者或管理员可查看邮箱
  },
});

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

Rate Limiting

限流配置

typescript
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)
  }
`;
typescript
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

错误处理

typescript
// 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;
  },
});
typescript
// 自定义错误格式化
const server = new ApolloServer({
  formatError: (formattedError, error) => {
    // 打印原始错误日志
    logger.error(error);

    // 不要泄露内部错误信息
    if (formattedError.extensions?.code === 'INTERNAL_SERVER_ERROR') {
      return {
        message: 'Internal server error',
        extensions: {
          code: 'INTERNAL_SERVER_ERROR',
        },
      };
    }

    // 生产环境移除堆栈信息
    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%
指标告警阈值
查询耗时p99> 500ms
错误率> 1%
平均复杂度得分> 500
超出深度限制错误> 10次/分钟
DataLoader缓存命中率< 50%

Pagination (Relay-style)

分页(Relay风格)

graphql
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
}
graphql
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

请求日志

typescript
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,
            });
          },
        };
      },
    },
  ],
});
typescript
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
  • 查询深度限制
  • 查询复杂度限制
  • 生产环境关闭自省功能
  • 用DataLoader防范N+1查询
  • 字段级权限控制
  • mutations接口限流
  • 自定义错误格式化
  • Relay风格分页
  • 带耗时统计的请求日志
  • 输入参数校验
  • 持久化查询(可选)
  • 开启APQ(自动持久化查询)

Code Generation

代码生成

GraphQL Codegen generates TypeScript types and hooks from your GraphQL schema and operations.
GraphQL Codegen可以根据你的GraphQL schema和操作生成TypeScript类型和钩子函数。

Quick Setup

快速配置

bash
npm install -D @graphql-codegen/cli @graphql-codegen/client-preset
typescript
// 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;
bash
npm install -D @graphql-codegen/cli @graphql-codegen/client-preset
typescript
// 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

生成代码使用示例

typescript
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>;
}
typescript
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
技能用途
GraphQL Codegen完整的codegen配置
TanStack Query数据拉取钩子
React API其他数据处理模式

Reference Documentation

参考文档

  • DataLoader
  • Authentication
  • Code Generation
  • DataLoader
  • Authentication
  • Code Generation