graphql
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGraphQL Core Knowledge
GraphQL核心知识
Deep Knowledge: Usewith technology:mcp__documentation__fetch_docsfor comprehensive documentation.graphql
深度知识: 使用技术参数为的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 skill)
rest-api - OpenAPI/Swagger documentation (use skill)
openapi - tRPC type-safe APIs (use skill)
trpc - Generating GraphQL types from schema (use skill)
graphql-codegen - 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
最佳实践
| 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 |
| 推荐做法 | 不推荐做法 |
|---|---|
| 为mutations使用input类型 | N+1查询(请使用DataLoader) |
| 实现分页能力 | 返回无边界列表 |
| 添加字段级鉴权 | 暴露敏感数据 |
| 使用fragments实现复用 | 过度拉取数据 |
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 | Type safety issues | Use |
| Returning null instead of errors | Poor error handling | Use proper GraphQL error responses |
| 反模式 | 弊端 | 解决方案 |
|---|---|---|
| N+1查询 | 导致性能问题、数据库过载 | 使用DataLoader做批量请求 |
| 在schema中暴露实现细节 | 耦合度高、难以重构 | 使用领域驱动的schema设计 |
| 列表没有分页能力 | 内存问题、响应缓慢 | 实现游标分页或偏移量分页 |
| 允许无边界的查询深度 | 存在DoS攻击风险 | 添加查询深度限制 |
| 没有查询复杂度限制 | 资源耗尽 | 添加复杂度分析校验 |
| 未做鉴权就暴露敏感字段 | 安全漏洞 | 添加字段级权限控制 |
使用 | 类型安全问题 | 使用 |
| 返回null而非错误信息 | 错误处理能力差 | 使用规范的GraphQL错误响应 |
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 |
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 查询性能缓慢 | 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
监控指标
| Metric | Alert 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-presettypescript
// 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-presettypescript
// 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
相关技能
| Skill | Purpose |
|---|---|
| GraphQL Codegen | Full codegen setup |
| TanStack Query | Data fetching hooks |
| React API | Alternative data patterns |
| 技能 | 用途 |
|---|---|
| GraphQL Codegen | 完整的codegen配置 |
| TanStack Query | 数据拉取钩子 |
| React API | 其他数据处理模式 |
Reference Documentation
参考文档
- DataLoader
- Authentication
- Code Generation
- DataLoader
- Authentication
- Code Generation