graphql-api-development

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

GraphQL API Development

GraphQL API 开发

A comprehensive skill for building production-ready GraphQL APIs using graphql-js. Master schema design, type systems, resolvers, queries, mutations, subscriptions, authentication, authorization, caching, testing, and deployment strategies.
这是一份使用graphql-js构建可用于生产环境的GraphQL API的综合指南。你将掌握Schema设计、类型系统、解析器、查询、变更、订阅、身份验证、授权、缓存、测试以及部署策略。

When to Use This Skill

何时使用本指南

Use this skill when:
  • Building a new API that requires flexible data fetching for web or mobile clients
  • Replacing or augmenting REST APIs with more efficient data access patterns
  • Developing APIs for applications with complex, nested data relationships
  • Creating APIs that serve multiple client types (web, mobile, desktop) with different data needs
  • Building real-time applications requiring subscriptions and live updates
  • Designing APIs where clients need to specify exactly what data they need
  • Developing GraphQL servers with Node.js and Express
  • Implementing type-safe APIs with strong schema validation
  • Creating self-documenting APIs with built-in introspection
  • Building microservices that need to be composed into a unified API
在以下场景使用本指南:
  • 构建需要为Web或移动客户端提供灵活数据获取能力的新API
  • 用更高效的数据访问模式替代或增强REST API
  • 开发具有复杂嵌套数据关系的应用API
  • 创建为多种客户端类型(Web、移动、桌面)提供服务且数据需求不同的API
  • 构建需要订阅和实时更新的实时应用
  • 设计允许客户端精确指定所需数据的API
  • 使用Node.js和Express开发GraphQL服务器
  • 实现具有强Schema验证的类型安全API
  • 创建内置自省功能的自文档化API
  • 构建需要组合成统一API的微服务

When GraphQL Excels Over REST

GraphQL 优于 REST 的场景

GraphQL Advantages

GraphQL 的优势

  1. Precise Data Fetching: Clients request exactly what they need, no over/under-fetching
  2. Single Request: Fetch multiple resources in one roundtrip instead of multiple REST endpoints
  3. Strongly Typed: Schema defines exact types, enabling validation and tooling
  4. Introspection: Self-documenting API with queryable schema
  5. Versioning Not Required: Add new fields without breaking existing queries
  6. Real-time Updates: Built-in subscription support for live data
  7. Nested Resources: Naturally handle complex relationships without N+1 queries
  8. Client-Driven: Clients control data shape, reducing backend changes
  1. 精准数据获取:客户端仅请求所需数据,避免过度获取或获取不足
  2. 单一请求:一次往返请求即可获取多个资源,无需调用多个REST端点
  3. 强类型约束:Schema定义精确类型,支持验证和工具集成
  4. 自省功能:具备可查询Schema的自文档化API
  5. 无需版本化:添加新字段不会破坏现有查询
  6. 实时更新:内置订阅支持以获取实时数据
  7. 嵌套资源:自然处理复杂关系,避免N+1查询问题
  8. 客户端驱动:客户端控制数据结构,减少后端变更需求

When to Stick with REST

何时仍选择 REST

  • Simple CRUD operations with standard resources
  • File uploads/downloads (GraphQL requires multipart handling)
  • HTTP caching is critical (GraphQL typically uses POST)
  • Team unfamiliar with GraphQL (learning curve)
  • Existing REST infrastructure works well
  • 具有标准资源的简单CRUD操作
  • 文件上传/下载(GraphQL需要多部分处理)
  • HTTP缓存至关重要的场景(GraphQL通常使用POST请求)
  • 团队不熟悉GraphQL(存在学习曲线)
  • 现有REST基础设施运行良好

Core Concepts

核心概念

The GraphQL Type System

GraphQL 类型系统

GraphQL's type system is its foundation. Every GraphQL API defines:
  1. Scalar Types: Basic data types (String, Int, Float, Boolean, ID)
  2. Object Types: Complex types with fields
  3. Query Type: Entry point for read operations
  4. Mutation Type: Entry point for write operations
  5. Subscription Type: Entry point for real-time updates
  6. Input Types: Complex inputs for mutations
  7. Enums: Fixed set of values
  8. Interfaces: Abstract types that objects implement
  9. Unions: Types that can be one of several types
  10. Non-Null Types: Types that cannot be null
  11. List Types: Arrays of types
GraphQL的类型系统是其基础。每个GraphQL API都定义了:
  1. 标量类型:基础数据类型(String、Int、Float、Boolean、ID)
  2. 对象类型:包含字段的复杂类型
  3. 查询类型:读取操作的入口点
  4. 变更类型:写入操作的入口点
  5. 订阅类型:实时更新的入口点
  6. 输入类型:用于变更的复杂输入
  7. 枚举类型:固定值集合
  8. 接口:对象实现的抽象类型
  9. 联合类型:可以是多种类型之一的类型
  10. 非空类型:不能为空的类型
  11. 列表类型:类型的数组

Schema Definition

Schema 定义

Two approaches for defining GraphQL schemas:
1. Schema Definition Language (SDL) - Declarative, readable:
graphql
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

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

type Query {
  user(id: ID!): User
  posts: [Post!]!
}
2. Programmatic API - Type-safe, programmatic:
javascript
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: new GraphQLNonNull(GraphQLID) },
    name: { type: new GraphQLNonNull(GraphQLString) },
    email: { type: new GraphQLNonNull(GraphQLString) },
    posts: { type: new GraphQLList(new GraphQLNonNull(PostType)) }
  }
});
定义GraphQL Schema有两种方式:
1. Schema定义语言(SDL) - 声明式、可读性强:
graphql
type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]!
}

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

type Query {
  user(id: ID!): User
  posts: [Post!]!
}
2. 程序化API - 类型安全、可编程:
javascript
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    id: { type: new GraphQLNonNull(GraphQLID) },
    name: { type: new GraphQLNonNull(GraphQLString) },
    email: { type: new GraphQLNonNull(GraphQLString) },
    posts: { type: new GraphQLList(new GraphQLNonNull(PostType)) }
  }
});

Resolvers

解析器

Resolvers are functions that return data for schema fields. Every field can have a resolver:
javascript
const resolvers = {
  Query: {
    user: (parent, args, context, info) => {
      return context.db.findUserById(args.id);
    }
  },
  User: {
    posts: (user, args, context) => {
      return context.db.findPostsByAuthorId(user.id);
    }
  }
};
Resolver Function Signature:
  • parent
    : The result from the parent resolver
  • args
    : Arguments passed to the field
  • context
    : Shared context (database, auth, etc.)
  • info
    : Field-specific metadata
解析器是为Schema字段返回数据的函数。每个字段都可以有对应的解析器:
javascript
const resolvers = {
  Query: {
    user: (parent, args, context, info) => {
      return context.db.findUserById(args.id);
    }
  },
  User: {
    posts: (user, args, context) => {
      return context.db.findPostsByAuthorId(user.id);
    }
  }
};
解析器函数签名:
  • parent
    :父解析器的结果
  • args
    :传递给字段的参数
  • context
    :共享上下文(数据库、认证信息等)
  • info
    :字段特定的元数据

Queries

查询

Queries fetch data from your API:
graphql
query GetUser {
  user(id: "123") {
    id
    name
    email
    posts {
      title
      content
    }
  }
}
查询用于从API获取数据:
graphql
query GetUser {
  user(id: "123") {
    id
    name
    email
    posts {
      title
      content
    }
  }
}

Mutations

变更

Mutations modify data:
graphql
mutation CreatePost {
  createPost(input: {
    title: "GraphQL is awesome"
    content: "Here's why..."
    authorId: "123"
  }) {
    id
    title
    author {
      name
    }
  }
}
变更用于修改数据:
graphql
mutation CreatePost {
  createPost(input: {
    title: "GraphQL is awesome"
    content: "Here's why..."
    authorId: "123"
  }) {
    id
    title
    author {
      name
    }
  }
}

Subscriptions

订阅

Subscriptions enable real-time updates:
graphql
subscription OnPostCreated {
  postCreated {
    id
    title
    author {
      name
    }
  }
}
订阅支持实时更新:
graphql
subscription OnPostCreated {
  postCreated {
    id
    title
    author {
      name
    }
  }
}

Schema Design Patterns

Schema 设计模式

Pattern 1: Input Types for Mutations

模式1:变更使用输入类型

Always use input types for complex mutation arguments:
graphql
input CreateUserInput {
  name: String!
  email: String!
  age: Int
  bio: String
}

type Mutation {
  createUser(input: CreateUserInput!): User!
}
Why: Easier to extend, better organization, reusable across mutations.
始终为复杂变更参数使用输入类型:
graphql
input CreateUserInput {
  name: String!
  email: String!
  age: Int
  bio: String
}

type Mutation {
  createUser(input: CreateUserInput!): User!
}
原因:易于扩展、组织性更好、可在多个变更中复用。

Pattern 2: Interfaces for Shared Fields

模式2:共享字段使用接口

Use interfaces when multiple types share fields:
graphql
interface Node {
  id: ID!
  createdAt: String!
  updatedAt: String!
}

type User implements Node {
  id: ID!
  createdAt: String!
  updatedAt: String!
  name: String!
  email: String!
}

type Post implements Node {
  id: ID!
  createdAt: String!
  updatedAt: String!
  title: String!
  content: String
}
当多个类型共享字段时使用接口:
graphql
interface Node {
  id: ID!
  createdAt: String!
  updatedAt: String!
}

type User implements Node {
  id: ID!
  createdAt: String!
  updatedAt: String!
  name: String!
  email: String!
}

type Post implements Node {
  id: ID!
  createdAt: String!
  updatedAt: String!
  title: String!
  content: String
}

Pattern 3: Unions for Polymorphic Returns

模式3:多态返回使用联合类型

Use unions when a field can return different types:
graphql
union SearchResult = User | Post | Comment

type Query {
  search(query: String!): [SearchResult!]!
}
当字段可以返回不同类型时使用联合类型:
graphql
union SearchResult = User | Post | Comment

type Query {
  search(query: String!): [SearchResult!]!
}

Pattern 4: Pagination Patterns

模式4:分页模式

Offset-based pagination:
graphql
type Query {
  posts(offset: Int, limit: Int): PostConnection!
}

type PostConnection {
  items: [Post!]!
  total: Int!
  hasMore: Boolean!
}
Cursor-based pagination (Relay-style):
graphql
type Query {
  posts(first: Int, after: String): PostConnection!
}

type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
}

type PostEdge {
  node: Post!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}
基于偏移量的分页:
graphql
type Query {
  posts(offset: Int, limit: Int): PostConnection!
}

type PostConnection {
  items: [Post!]!
  total: Int!
  hasMore: Boolean!
}
基于游标分页(Relay风格):
graphql
type Query {
  posts(first: Int, after: String): PostConnection!
}

type PostConnection {
  edges: [PostEdge!]!
  pageInfo: PageInfo!
}

type PostEdge {
  node: Post!
  cursor: String!
}

type PageInfo {
  hasNextPage: Boolean!
  endCursor: String
}

Pattern 5: Error Handling

模式5:错误处理

Field-level errors:
graphql
type MutationPayload {
  success: Boolean!
  message: String
  user: User
  errors: [Error!]
}

type Error {
  field: String!
  message: String!
}
Union-based error handling:
graphql
union CreateUserResult = User | ValidationError | DatabaseError

type ValidationError {
  field: String!
  message: String!
}
字段级错误:
graphql
type MutationPayload {
  success: Boolean!
  message: String
  user: User
  errors: [Error!]
}

type Error {
  field: String!
  message: String!
}
基于联合类型的错误处理:
graphql
union CreateUserResult = User | ValidationError | DatabaseError

type ValidationError {
  field: String!
  message: String!
}

Pattern 6: Versioning with Directives

模式6:使用指令进行版本控制

Deprecate fields instead of versioning:
graphql
type User {
  name: String! @deprecated(reason: "Use firstName and lastName")
  firstName: String!
  lastName: String!
}
通过弃用字段替代版本化:
graphql
type User {
  name: String! @deprecated(reason: "Use firstName and lastName")
  firstName: String!
  lastName: String!
}

Query Optimization and Performance

查询优化与性能

The N+1 Problem

N+1问题

Problem: Fetching nested data causes multiple database queries:
javascript
// BAD: N+1 queries
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    posts: {
      type: new GraphQLList(PostType),
      resolve: (user) => {
        // This runs once PER user!
        return db.getPostsByUserId(user.id);
      }
    }
  }
});

// Query for 100 users = 1 query for users + 100 queries for posts = 101 queries
问题:获取嵌套数据会导致多次数据库查询:
javascript
// BAD: N+1 queries
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    posts: {
      type: new GraphQLList(PostType),
      resolve: (user) => {
        // This runs once PER user!
        return db.getPostsByUserId(user.id);
      }
    }
  }
});

// Query for 100 users = 1 query for users + 100 queries for posts = 101 queries

DataLoader Solution

DataLoader解决方案

DataLoader batches and caches requests:
javascript
import DataLoader from 'dataloader';

// Create DataLoader
const postLoader = new DataLoader(async (userIds) => {
  // Single query for all user IDs
  const posts = await db.getPostsByUserIds(userIds);

  // Group posts by userId
  const postsByUserId = {};
  posts.forEach(post => {
    if (!postsByUserId[post.authorId]) {
      postsByUserId[post.authorId] = [];
    }
    postsByUserId[post.authorId].push(post);
  });

  // Return in same order as userIds
  return userIds.map(id => postsByUserId[id] || []);
});

// Use in resolver
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    posts: {
      type: new GraphQLList(PostType),
      resolve: (user, args, context) => {
        return context.loaders.postLoader.load(user.id);
      }
    }
  }
});

// Add to context
const context = {
  loaders: {
    postLoader: new DataLoader(batchLoadPosts)
  }
};
DataLoader用于批量处理和缓存请求:
javascript
import DataLoader from 'dataloader';

// Create DataLoader
const postLoader = new DataLoader(async (userIds) => {
  // Single query for all user IDs
  const posts = await db.getPostsByUserIds(userIds);

  // Group posts by userId
  const postsByUserId = {};
  posts.forEach(post => {
    if (!postsByUserId[post.authorId]) {
      postsByUserId[post.authorId] = [];
    }
    postsByUserId[post.authorId].push(post);
  });

  // Return in same order as userIds
  return userIds.map(id => postsByUserId[id] || []);
});

// Use in resolver
const UserType = new GraphQLObjectType({
  name: 'User',
  fields: {
    posts: {
      type: new GraphQLList(PostType),
      resolve: (user, args, context) => {
        return context.loaders.postLoader.load(user.id);
      }
    }
  }
});

// Add to context
const context = {
  loaders: {
    postLoader: new DataLoader(batchLoadPosts)
  }
};

Query Complexity Analysis

查询复杂度分析

Limit expensive queries:
javascript
import { getComplexity, simpleEstimator } from 'graphql-query-complexity';

const complexity = getComplexity({
  schema,
  query,
  estimators: [
    simpleEstimator({ defaultComplexity: 1 })
  ]
});

if (complexity > 1000) {
  throw new Error('Query too complex');
}
限制资源密集型查询:
javascript
import { getComplexity, simpleEstimator } from 'graphql-query-complexity';

const complexity = getComplexity({
  schema,
  query,
  estimators: [
    simpleEstimator({ defaultComplexity: 1 })
  ]
});

if (complexity > 1000) {
  throw new Error('Query too complex');
}

Depth Limiting

深度限制

Prevent deeply nested queries:
javascript
import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({
  schema,
  validationRules: [depthLimit(5)]
});
防止深度嵌套查询:
javascript
import depthLimit from 'graphql-depth-limit';

const server = new ApolloServer({
  schema,
  validationRules: [depthLimit(5)]
});

Mutations and Input Validation

变更与输入验证

Mutation Design Pattern

变更设计模式

graphql
input CreatePostInput {
  title: String!
  content: String!
  authorId: ID!
  tags: [String!]
}

type CreatePostPayload {
  post: Post
  errors: [UserError!]
  success: Boolean!
}

type UserError {
  message: String!
  field: String
}

type Mutation {
  createPost(input: CreatePostInput!): CreatePostPayload!
}
graphql
input CreatePostInput {
  title: String!
  content: String!
  authorId: ID!
  tags: [String!]
}

type CreatePostPayload {
  post: Post
  errors: [UserError!]
  success: Boolean!
}

type UserError {
  message: String!
  field: String
}

type Mutation {
  createPost(input: CreatePostInput!): CreatePostPayload!
}

Input Validation

输入验证

javascript
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    createPost: {
      type: CreatePostPayload,
      args: {
        input: { type: new GraphQLNonNull(CreatePostInput) }
      },
      resolve: async (_, { input }, context) => {
        // Validate input
        const errors = [];

        if (input.title.length < 3) {
          errors.push({
            field: 'title',
            message: 'Title must be at least 3 characters'
          });
        }

        if (input.content.length < 10) {
          errors.push({
            field: 'content',
            message: 'Content must be at least 10 characters'
          });
        }

        if (errors.length > 0) {
          return { errors, success: false, post: null };
        }

        // Create post
        const post = await context.db.createPost(input);
        return { post, errors: [], success: true };
      }
    }
  }
});
javascript
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    createPost: {
      type: CreatePostPayload,
      args: {
        input: { type: new GraphQLNonNull(CreatePostInput) }
      },
      resolve: async (_, { input }, context) => {
        // Validate input
        const errors = [];

        if (input.title.length < 3) {
          errors.push({
            field: 'title',
            message: 'Title must be at least 3 characters'
          });
        }

        if (input.content.length < 10) {
          errors.push({
            field: 'content',
            message: 'Content must be at least 10 characters'
          });
        }

        if (errors.length > 0) {
          return { errors, success: false, post: null };
        }

        // Create post
        const post = await context.db.createPost(input);
        return { post, errors: [], success: true };
      }
    }
  }
});

Subscriptions and Real-time Updates

订阅与实时更新

Setting Up Subscriptions

设置订阅

javascript
import { GraphQLObjectType, GraphQLString } from 'graphql';
import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

const Subscription = new GraphQLObjectType({
  name: 'Subscription',
  fields: {
    postCreated: {
      type: PostType,
      subscribe: () => pubsub.asyncIterator(['POST_CREATED'])
    },
    messageReceived: {
      type: MessageType,
      args: {
        channelId: { type: new GraphQLNonNull(GraphQLID) }
      },
      subscribe: (_, { channelId }) => {
        return pubsub.asyncIterator([`MESSAGE_${channelId}`]);
      }
    }
  }
});
javascript
import { GraphQLObjectType, GraphQLString } from 'graphql';
import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

const Subscription = new GraphQLObjectType({
  name: 'Subscription',
  fields: {
    postCreated: {
      type: PostType,
      subscribe: () => pubsub.asyncIterator(['POST_CREATED'])
    },
    messageReceived: {
      type: MessageType,
      args: {
        channelId: { type: new GraphQLNonNull(GraphQLID) }
      },
      subscribe: (_, { channelId }) => {
        return pubsub.asyncIterator([`MESSAGE_${channelId}`]);
      }
    }
  }
});

Publishing Events

发布事件

javascript
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    createPost: {
      type: PostType,
      args: {
        input: { type: new GraphQLNonNull(CreatePostInput) }
      },
      resolve: async (_, { input }, context) => {
        const post = await context.db.createPost(input);

        // Publish to subscribers
        pubsub.publish('POST_CREATED', { postCreated: post });

        return post;
      }
    }
  }
});
javascript
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    createPost: {
      type: PostType,
      args: {
        input: { type: new GraphQLNonNull(CreatePostInput) }
      },
      resolve: async (_, { input }, context) => {
        const post = await context.db.createPost(input);

        // Publish to subscribers
        pubsub.publish('POST_CREATED', { postCreated: post });

        return post;
      }
    }
  }
});

WebSocket Server Setup

WebSocket服务器设置

javascript
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { execute, subscribe } from 'graphql';
import express from 'express';

const app = express();
const httpServer = createServer(app);

// WebSocket server for subscriptions
const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql'
});

useServer(
  {
    schema,
    execute,
    subscribe,
    context: (ctx) => {
      // Access connection params, headers
      return {
        userId: ctx.connectionParams?.userId,
        db: database
      };
    }
  },
  wsServer
);

httpServer.listen(4000);
javascript
import { createServer } from 'http';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import { execute, subscribe } from 'graphql';
import express from 'express';

const app = express();
const httpServer = createServer(app);

// WebSocket server for subscriptions
const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql'
});

useServer(
  {
    schema,
    execute,
    subscribe,
    context: (ctx) => {
      // Access connection params, headers
      return {
        userId: ctx.connectionParams?.userId,
        db: database
      };
    }
  },
  wsServer
);

httpServer.listen(4000);

Authentication and Authorization

身份验证与授权

Context-Based Authentication

基于上下文的身份验证

javascript
import jwt from 'jsonwebtoken';

// Middleware to extract user
const authMiddleware = async (req) => {
  const token = req.headers.authorization?.replace('Bearer ', '');

  if (!token) {
    return { user: null };
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    const user = await db.findUserById(decoded.userId);
    return { user };
  } catch (error) {
    return { user: null };
  }
};

// Add to GraphQL context
app.all('/graphql', async (req, res) => {
  const auth = await authMiddleware(req);

  createHandler({
    schema,
    context: {
      user: auth.user,
      db: database
    }
  })(req, res);
});
javascript
import jwt from 'jsonwebtoken';

// Middleware to extract user
const authMiddleware = async (req) => {
  const token = req.headers.authorization?.replace('Bearer ', '');

  if (!token) {
    return { user: null };
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    const user = await db.findUserById(decoded.userId);
    return { user };
  } catch (error) {
    return { user: null };
  }
};

// Add to GraphQL context
app.all('/graphql', async (req, res) => {
  const auth = await authMiddleware(req);

  createHandler({
    schema,
    context: {
      user: auth.user,
      db: database
    }
  })(req, res);
});

Resolver-Level Authorization

解析器级授权

javascript
const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    me: {
      type: UserType,
      resolve: (_, __, context) => {
        if (!context.user) {
          throw new Error('Authentication required');
        }
        return context.user;
      }
    },
    adminData: {
      type: GraphQLString,
      resolve: (_, __, context) => {
        if (!context.user) {
          throw new Error('Authentication required');
        }

        if (context.user.role !== 'admin') {
          throw new Error('Admin access required');
        }

        return 'Secret admin data';
      }
    }
  }
});
javascript
const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    me: {
      type: UserType,
      resolve: (_, __, context) => {
        if (!context.user) {
          throw new Error('Authentication required');
        }
        return context.user;
      }
    },
    adminData: {
      type: GraphQLString,
      resolve: (_, __, context) => {
        if (!context.user) {
          throw new Error('Authentication required');
        }

        if (context.user.role !== 'admin') {
          throw new Error('Admin access required');
        }

        return 'Secret admin data';
      }
    }
  }
});

Field-Level Authorization

字段级授权

javascript
const PostType = new GraphQLObjectType({
  name: 'Post',
  fields: {
    title: { type: GraphQLString },
    content: { type: GraphQLString },
    draft: {
      type: GraphQLBoolean,
      resolve: (post, args, context) => {
        // Only author can see draft status
        if (post.authorId !== context.user?.id) {
          return null;
        }
        return post.draft;
      }
    }
  }
});
javascript
const PostType = new GraphQLObjectType({
  name: 'Post',
  fields: {
    title: { type: GraphQLString },
    content: { type: GraphQLString },
    draft: {
      type: GraphQLBoolean,
      resolve: (post, args, context) => {
        // Only author can see draft status
        if (post.authorId !== context.user?.id) {
          return null;
        }
        return post.draft;
      }
    }
  }
});

Directive-Based Authorization

基于指令的授权

graphql
directive @auth(requires: Role = USER) on FIELD_DEFINITION

enum Role {
  USER
  ADMIN
  MODERATOR
}

type Query {
  publicData: String
  userData: String @auth(requires: USER)
  adminData: String @auth(requires: ADMIN)
}
javascript
import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils';

function authDirective(schema, directiveName) {
  return mapSchema(schema, {
    [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
      const authDirective = getDirective(schema, fieldConfig, directiveName)?.[0];

      if (authDirective) {
        const { requires } = authDirective;
        const { resolve = defaultFieldResolver } = fieldConfig;

        fieldConfig.resolve = async (source, args, context, info) => {
          if (!context.user) {
            throw new Error('Authentication required');
          }

          if (context.user.role !== requires) {
            throw new Error(`${requires} role required`);
          }

          return resolve(source, args, context, info);
        };
      }

      return fieldConfig;
    }
  });
}
graphql
directive @auth(requires: Role = USER) on FIELD_DEFINITION

enum Role {
  USER
  ADMIN
  MODERATOR
}

type Query {
  publicData: String
  userData: String @auth(requires: USER)
  adminData: String @auth(requires: ADMIN)
}
javascript
import { mapSchema, getDirective, MapperKind } from '@graphql-tools/utils';

function authDirective(schema, directiveName) {
  return mapSchema(schema, {
    [MapperKind.OBJECT_FIELD]: (fieldConfig) => {
      const authDirective = getDirective(schema, fieldConfig, directiveName)?.[0];

      if (authDirective) {
        const { requires } = authDirective;
        const { resolve = defaultFieldResolver } = fieldConfig;

        fieldConfig.resolve = async (source, args, context, info) => {
          if (!context.user) {
            throw new Error('Authentication required');
          }

          if (context.user.role !== requires) {
            throw new Error(`${requires} role required`);
          }

          return resolve(source, args, context, info);
        };
      }

      return fieldConfig;
    }
  });
}

Caching Strategies

缓存策略

In-Memory Caching

内存缓存

javascript
import { LRUCache } from 'lru-cache';

const cache = new LRUCache({
  max: 500,
  ttl: 1000 * 60 * 5 // 5 minutes
});

const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    product: {
      type: ProductType,
      args: { id: { type: new GraphQLNonNull(GraphQLID) } },
      resolve: async (_, { id }, context) => {
        const cacheKey = `product:${id}`;
        const cached = cache.get(cacheKey);

        if (cached) {
          return cached;
        }

        const product = await context.db.findProductById(id);
        cache.set(cacheKey, product);
        return product;
      }
    }
  }
});
javascript
import { LRUCache } from 'lru-cache';

const cache = new LRUCache({
  max: 500,
  ttl: 1000 * 60 * 5 // 5 minutes
});

const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    product: {
      type: ProductType,
      args: { id: { type: new GraphQLNonNull(GraphQLID) } },
      resolve: async (_, { id }, context) => {
        const cacheKey = `product:${id}`;
        const cached = cache.get(cacheKey);

        if (cached) {
          return cached;
        }

        const product = await context.db.findProductById(id);
        cache.set(cacheKey, product);
        return product;
      }
    }
  }
});

Redis Caching

Redis缓存

javascript
import Redis from 'ioredis';

const redis = new Redis({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT
});

const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: UserType,
      args: { id: { type: new GraphQLNonNull(GraphQLID) } },
      resolve: async (_, { id }, context) => {
        const cacheKey = `user:${id}`;

        // Check cache
        const cached = await redis.get(cacheKey);
        if (cached) {
          return JSON.parse(cached);
        }

        // Fetch from database
        const user = await context.db.findUserById(id);

        // Cache for 10 minutes
        await redis.setex(cacheKey, 600, JSON.stringify(user));

        return user;
      }
    }
  }
});
javascript
import Redis from 'ioredis';

const redis = new Redis({
  host: process.env.REDIS_HOST,
  port: process.env.REDIS_PORT
});

const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: UserType,
      args: { id: { type: new GraphQLNonNull(GraphQLID) } },
      resolve: async (_, { id }, context) => {
        const cacheKey = `user:${id}`;

        // Check cache
        const cached = await redis.get(cacheKey);
        if (cached) {
          return JSON.parse(cached);
        }

        // Fetch from database
        const user = await context.db.findUserById(id);

        // Cache for 10 minutes
        await redis.setex(cacheKey, 600, JSON.stringify(user));

        return user;
      }
    }
  }
});

Cache Invalidation

缓存失效

javascript
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    updateUser: {
      type: UserType,
      args: {
        id: { type: new GraphQLNonNull(GraphQLID) },
        input: { type: new GraphQLNonNull(UpdateUserInput) }
      },
      resolve: async (_, { id, input }, context) => {
        const user = await context.db.updateUser(id, input);

        // Invalidate cache
        const cacheKey = `user:${id}`;
        await redis.del(cacheKey);

        // Also invalidate list caches
        await redis.del('users:all');

        return user;
      }
    }
  }
});
javascript
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    updateUser: {
      type: UserType,
      args: {
        id: { type: new GraphQLNonNull(GraphQLID) },
        input: { type: new GraphQLNonNull(UpdateUserInput) }
      },
      resolve: async (_, { id, input }, context) => {
        const user = await context.db.updateUser(id, input);

        // Invalidate cache
        const cacheKey = `user:${id}`;
        await redis.del(cacheKey);

        // Also invalidate list caches
        await redis.del('users:all');

        return user;
      }
    }
  }
});

Error Handling

错误处理

Custom Error Classes

自定义错误类

javascript
class AuthenticationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'AuthenticationError';
    this.extensions = { code: 'UNAUTHENTICATED' };
  }
}

class ForbiddenError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ForbiddenError';
    this.extensions = { code: 'FORBIDDEN' };
  }
}

class ValidationError extends Error {
  constructor(message, fields) {
    super(message);
    this.name = 'ValidationError';
    this.extensions = {
      code: 'BAD_USER_INPUT',
      fields
    };
  }
}
javascript
class AuthenticationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'AuthenticationError';
    this.extensions = { code: 'UNAUTHENTICATED' };
  }
}

class ForbiddenError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ForbiddenError';
    this.extensions = { code: 'FORBIDDEN' };
  }
}

class ValidationError extends Error {
  constructor(message, fields) {
    super(message);
    this.name = 'ValidationError';
    this.extensions = {
      code: 'BAD_USER_INPUT',
      fields
    };
  }
}

Error Formatting

错误格式化

javascript
import { formatError } from 'graphql';

const customFormatError = (error) => {
  // Log error for monitoring
  console.error('GraphQL Error:', {
    message: error.message,
    locations: error.locations,
    path: error.path,
    extensions: error.extensions
  });

  // Don't expose internal errors to clients
  if (error.message.startsWith('Database')) {
    return {
      message: 'Internal server error',
      extensions: { code: 'INTERNAL_SERVER_ERROR' }
    };
  }

  return formatError(error);
};

const server = new ApolloServer({
  schema,
  formatError: customFormatError
});
javascript
import { formatError } from 'graphql';

const customFormatError = (error) => {
  // Log error for monitoring
  console.error('GraphQL Error:', {
    message: error.message,
    locations: error.locations,
    path: error.path,
    extensions: error.extensions
  });

  // Don't expose internal errors to clients
  if (error.message.startsWith('Database')) {
    return {
      message: 'Internal server error',
      extensions: { code: 'INTERNAL_SERVER_ERROR' }
    };
  }

  return formatError(error);
};

const server = new ApolloServer({
  schema,
  formatError: customFormatError
});

Graceful Error Responses

优雅的错误响应

javascript
const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: UserType,
      args: { id: { type: new GraphQLNonNull(GraphQLID) } },
      resolve: async (_, { id }, context) => {
        try {
          const user = await context.db.findUserById(id);

          if (!user) {
            throw new Error(`User with ID ${id} not found`);
          }

          return user;
        } catch (error) {
          // Log error
          console.error('Error fetching user:', error);

          // Re-throw with user-friendly message
          if (error.code === 'ECONNREFUSED') {
            throw new Error('Unable to connect to database');
          }

          throw error;
        }
      }
    }
  }
});
javascript
const Query = new GraphQLObjectType({
  name: 'Query',
  fields: {
    user: {
      type: UserType,
      args: { id: { type: new GraphQLNonNull(GraphQLID) } },
      resolve: async (_, { id }, context) => {
        try {
          const user = await context.db.findUserById(id);

          if (!user) {
            throw new Error(`User with ID ${id} not found`);
          }

          return user;
        } catch (error) {
          // Log error
          console.error('Error fetching user:', error);

          // Re-throw with user-friendly message
          if (error.code === 'ECONNREFUSED') {
            throw new Error('Unable to connect to database');
          }

          throw error;
        }
      }
    }
  }
});

Testing GraphQL APIs

测试GraphQL API

Unit Testing Resolvers

解析器单元测试

javascript
import { describe, it, expect, jest } from '@jest/globals';

describe('User resolver', () => {
  it('returns user by ID', async () => {
    const mockDb = {
      findUserById: jest.fn().mockResolvedValue({
        id: '1',
        name: 'Alice',
        email: 'alice@example.com'
      })
    };

    const context = { db: mockDb };
    const result = await userResolver.resolve(null, { id: '1' }, context);

    expect(mockDb.findUserById).toHaveBeenCalledWith('1');
    expect(result).toEqual({
      id: '1',
      name: 'Alice',
      email: 'alice@example.com'
    });
  });

  it('throws error for non-existent user', async () => {
    const mockDb = {
      findUserById: jest.fn().mockResolvedValue(null)
    };

    const context = { db: mockDb };

    await expect(
      userResolver.resolve(null, { id: '999' }, context)
    ).rejects.toThrow('User with ID 999 not found');
  });
});
javascript
import { describe, it, expect, jest } from '@jest/globals';

describe('User resolver', () => {
  it('returns user by ID', async () => {
    const mockDb = {
      findUserById: jest.fn().mockResolvedValue({
        id: '1',
        name: 'Alice',
        email: 'alice@example.com'
      })
    };

    const context = { db: mockDb };
    const result = await userResolver.resolve(null, { id: '1' }, context);

    expect(mockDb.findUserById).toHaveBeenCalledWith('1');
    expect(result).toEqual({
      id: '1',
      name: 'Alice',
      email: 'alice@example.com'
    });
  });

  it('throws error for non-existent user', async () => {
    const mockDb = {
      findUserById: jest.fn().mockResolvedValue(null)
    };

    const context = { db: mockDb };

    await expect(
      userResolver.resolve(null, { id: '999' }, context)
    ).rejects.toThrow('User with ID 999 not found');
  });
});

Integration Testing

集成测试

javascript
import { graphql } from 'graphql';
import { schema } from './schema';

describe('GraphQL Schema', () => {
  it('executes user query', async () => {
    const query = `
      query {
        user(id: "1") {
          id
          name
          email
        }
      }
    `;

    const result = await graphql({
      schema,
      source: query,
      contextValue: {
        db: mockDatabase,
        user: null
      }
    });

    expect(result.errors).toBeUndefined();
    expect(result.data?.user).toEqual({
      id: '1',
      name: 'Alice',
      email: 'alice@example.com'
    });
  });

  it('handles authentication errors', async () => {
    const query = `
      query {
        me {
          id
          name
        }
      }
    `;

    const result = await graphql({
      schema,
      source: query,
      contextValue: {
        db: mockDatabase,
        user: null
      }
    });

    expect(result.errors).toBeDefined();
    expect(result.errors[0].message).toBe('Authentication required');
  });
});
javascript
import { graphql } from 'graphql';
import { schema } from './schema';

describe('GraphQL Schema', () => {
  it('executes user query', async () => {
    const query = `
      query {
        user(id: "1") {
          id
          name
          email
        }
      }
    `;

    const result = await graphql({
      schema,
      source: query,
      contextValue: {
        db: mockDatabase,
        user: null
      }
    });

    expect(result.errors).toBeUndefined();
    expect(result.data?.user).toEqual({
      id: '1',
      name: 'Alice',
      email: 'alice@example.com'
    });
  });

  it('handles authentication errors', async () => {
    const query = `
      query {
        me {
          id
          name
        }
      }
    `;

    const result = await graphql({
      schema,
      source: query,
      contextValue: {
        db: mockDatabase,
        user: null
      }
    });

    expect(result.errors).toBeDefined();
    expect(result.errors[0].message).toBe('Authentication required');
  });
});

Testing with Apollo Server

使用Apollo Server测试

javascript
import { ApolloServer } from '@apollo/server';

const testServer = new ApolloServer({
  schema,
});

describe('User queries', () => {
  it('fetches user successfully', async () => {
    const response = await testServer.executeOperation({
      query: `
        query GetUser($id: ID!) {
          user(id: $id) {
            id
            name
          }
        }
      `,
      variables: { id: '1' }
    });

    expect(response.body.singleResult.errors).toBeUndefined();
    expect(response.body.singleResult.data?.user).toMatchObject({
      id: '1',
      name: expect.any(String)
    });
  });
});
javascript
import { ApolloServer } from '@apollo/server';

const testServer = new ApolloServer({
  schema,
});

describe('User queries', () => {
  it('fetches user successfully', async () => {
    const response = await testServer.executeOperation({
      query: `
        query GetUser($id: ID!) {
          user(id: $id) {
            id
            name
          }
        }
      `,
      variables: { id: '1' }
    });

    expect(response.body.singleResult.errors).toBeUndefined();
    expect(response.body.singleResult.data?.user).toMatchObject({
      id: '1',
      name: expect.any(String)
    });
  });
});

Production Best Practices

生产环境最佳实践

Schema Organization

Schema 组织

src/
├── schema/
│   ├── index.js          # Combine all types
│   ├── types/
│   │   ├── user.js       # User type and resolvers
│   │   ├── post.js       # Post type and resolvers
│   │   └── comment.js    # Comment type and resolvers
│   ├── queries/
│   │   ├── user.js       # User queries
│   │   └── post.js       # Post queries
│   ├── mutations/
│   │   ├── user.js       # User mutations
│   │   └── post.js       # Post mutations
│   └── subscriptions/
│       └── post.js       # Post subscriptions
├── directives/
│   └── auth.js           # Authorization directive
├── utils/
│   ├── loaders.js        # DataLoader instances
│   └── context.js        # Context builder
└── server.js             # Server setup
src/
├── schema/
│   ├── index.js          # 合并所有类型
│   ├── types/
│   │   ├── user.js       # User类型与解析器
│   │   ├── post.js       # Post类型与解析器
│   │   └── comment.js    # Comment类型与解析器
│   ├── queries/
│   │   ├── user.js       # User查询
│   │   └── post.js       # Post查询
│   ├── mutations/
│   │   ├── user.js       # User变更
│   │   └── post.js       # Post变更
│   └── subscriptions/
│       └── post.js       # Post订阅
├── directives/
│   └── auth.js           # 授权指令
├── utils/
│   ├── loaders.js        # DataLoader实例
│   └── context.js        # 上下文构建器
└── server.js             # 服务器设置

Monitoring and Logging

监控与日志

javascript
import { ApolloServerPluginLandingPageGraphQLPlayground } from '@apollo/server-plugin-landing-page-graphql-playground';

const server = new ApolloServer({
  schema,
  plugins: [
    // Request logging
    {
      async requestDidStart(requestContext) {
        console.log('Request started:', requestContext.request.query);

        return {
          async didEncounterErrors(ctx) {
            console.error('Errors:', ctx.errors);
          },
          async willSendResponse(ctx) {
            console.log('Response sent');
          }
        };
      }
    },

    // Performance monitoring
    {
      async requestDidStart() {
        const start = Date.now();

        return {
          async willSendResponse() {
            const duration = Date.now() - start;
            console.log(`Request duration: ${duration}ms`);
          }
        };
      }
    }
  ]
});
javascript
import { ApolloServerPluginLandingPageGraphQLPlayground } from '@apollo/server-plugin-landing-page-graphql-playground';

const server = new ApolloServer({
  schema,
  plugins: [
    // 请求日志
    {
      async requestDidStart(requestContext) {
        console.log('Request started:', requestContext.request.query);

        return {
          async didEncounterErrors(ctx) {
            console.error('Errors:', ctx.errors);
          },
          async willSendResponse(ctx) {
            console.log('Response sent');
          }
        };
      }
    },

    // 性能监控
    {
      async requestDidStart() {
        const start = Date.now();

        return {
          async willSendResponse() {
            const duration = Date.now() - start;
            console.log(`Request duration: ${duration}ms`);
          }
        };
      }
    }
  ]
});

Rate Limiting

请求频率限制

javascript
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per window
  message: 'Too many requests, please try again later'
});

app.use('/graphql', limiter);
javascript
import rateLimit from 'express-rate-limit';

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15分钟
  max: 100, // 限制每个IP在窗口时间内最多100次请求
  message: '请求过于频繁,请稍后再试'
});

app.use('/graphql', limiter);

Query Whitelisting

查询白名单

javascript
const allowedQueries = new Set([
  'query GetUser { user(id: $id) { id name email } }',
  'mutation CreatePost { createPost(input: $input) { id title } }'
]);

const validateQuery = (query) => {
  const normalized = query.replace(/\s+/g, ' ').trim();
  if (!allowedQueries.has(normalized)) {
    throw new Error('Query not whitelisted');
  }
};
javascript
const allowedQueries = new Set([
  'query GetUser { user(id: $id) { id name email } }',
  'mutation CreatePost { createPost(input: $input) { id title } }'
]);

const validateQuery = (query) => {
  const normalized = query.replace(/\s+/g, ' ').trim();
  if (!allowedQueries.has(normalized)) {
    throw new Error('Query not whitelisted');
  }
};

Security Headers

安全头

javascript
import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"],
    }
  },
  crossOriginEmbedderPolicy: false
}));
javascript
import helmet from 'helmet';

app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'self'"],
      scriptSrc: ["'self'", "'unsafe-inline'"],
    }
  },
  crossOriginEmbedderPolicy: false
}));

Advanced Patterns

高级模式

Federation (Microservices)

联邦(微服务)

javascript
import { buildSubgraphSchema } from '@apollo/subgraph';

// Users service
const userSchema = buildSubgraphSchema({
  typeDefs: `
    type User @key(fields: "id") {
      id: ID!
      name: String!
      email: String!
    }
  `,
  resolvers: {
    User: {
      __resolveReference(user) {
        return findUserById(user.id);
      }
    }
  }
});

// Posts service
const postSchema = buildSubgraphSchema({
  typeDefs: `
    type Post {
      id: ID!
      title: String!
      author: User!
    }

    extend type User @key(fields: "id") {
      id: ID! @external
      posts: [Post!]!
    }
  `,
  resolvers: {
    Post: {
      author(post) {
        return { __typename: 'User', id: post.authorId };
      }
    },
    User: {
      posts(user) {
        return findPostsByAuthorId(user.id);
      }
    }
  }
});
javascript
import { buildSubgraphSchema } from '@apollo/subgraph';

// 用户服务
const userSchema = buildSubgraphSchema({
  typeDefs: `
    type User @key(fields: "id") {
      id: ID!
      name: String!
      email: String!
    }
  `,
  resolvers: {
    User: {
      __resolveReference(user) {
        return findUserById(user.id);
      }
    }
  }
});

// 帖子服务
const postSchema = buildSubgraphSchema({
  typeDefs: `
    type Post {
      id: ID!
      title: String!
      author: User!
    }

    extend type User @key(fields: "id") {
      id: ID! @external
      posts: [Post!]!
    }
  `,
  resolvers: {
    Post: {
      author(post) {
        return { __typename: 'User', id: post.authorId };
      }
    },
    User: {
      posts(user) {
        return findPostsByAuthorId(user.id);
      }
    }
  }
});

Custom Scalars

自定义标量

javascript
import { GraphQLScalarType, Kind } from 'graphql';

const DateTimeScalar = new GraphQLScalarType({
  name: 'DateTime',
  description: 'ISO-8601 DateTime string',

  serialize(value) {
    // Send to client
    return value instanceof Date ? value.toISOString() : null;
  },

  parseValue(value) {
    // From variables
    return new Date(value);
  },

  parseLiteral(ast) {
    // From query string
    if (ast.kind === Kind.STRING) {
      return new Date(ast.value);
    }
    return null;
  }
});

// Use in schema
const schema = new GraphQLSchema({
  types: [DateTimeScalar],
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      now: {
        type: DateTimeScalar,
        resolve: () => new Date()
      }
    }
  })
});
javascript
import { GraphQLScalarType, Kind } from 'graphql';

const DateTimeScalar = new GraphQLScalarType({
  name: 'DateTime',
  description: 'ISO-8601 DateTime string',

  serialize(value) {
    // Send to client
    return value instanceof Date ? value.toISOString() : null;
  },

  parseValue(value) {
    // From variables
    return new Date(value);
  },

  parseLiteral(ast) {
    // From query string
    if (ast.kind === Kind.STRING) {
      return new Date(ast.value);
    }
    return null;
  }
});

// Use in schema
const schema = new GraphQLSchema({
  types: [DateTimeScalar],
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      now: {
        type: DateTimeScalar,
        resolve: () => new Date()
      }
    }
  })
});

Batch Operations

批量操作

javascript
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    batchCreateUsers: {
      type: new GraphQLList(UserType),
      args: {
        inputs: {
          type: new GraphQLNonNull(
            new GraphQLList(new GraphQLNonNull(CreateUserInput))
          )
        }
      },
      resolve: async (_, { inputs }, context) => {
        const users = await Promise.all(
          inputs.map(input => context.db.createUser(input))
        );
        return users;
      }
    }
  }
});
javascript
const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    batchCreateUsers: {
      type: new GraphQLList(UserType),
      args: {
        inputs: {
          type: new GraphQLNonNull(
            new GraphQLList(new GraphQLNonNull(CreateUserInput))
          )
        }
      },
      resolve: async (_, { inputs }, context) => {
        const users = await Promise.all(
          inputs.map(input => context.db.createUser(input))
        );
        return users;
      }
    }
  }
});

Common Patterns Summary

常见模式总结

  1. Use Input Types: For all mutations with multiple arguments
  2. Implement DataLoader: Solve N+1 queries for nested data
  3. Add Pagination: For list fields that can grow unbounded
  4. Handle Errors Gracefully: Return user-friendly error messages
  5. Validate Inputs: At resolver level before database operations
  6. Use Context for Shared State: Database, authentication, loaders
  7. Implement Authorization: At resolver or directive level
  8. Cache Aggressively: Use Redis or in-memory for frequently accessed data
  9. Monitor Performance: Track query complexity and execution time
  10. Version with @deprecated: Never break existing queries
  11. Test Thoroughly: Unit test resolvers, integration test queries
  12. Document Schema: Use descriptions in SDL
  13. Use Non-Null Wisely: Only for truly required fields
  14. Organize Schema: Split into modules by domain
  15. Secure Production: Rate limiting, query whitelisting, depth limiting
  1. 使用输入类型:所有包含多个参数的变更都使用输入类型
  2. 实现DataLoader:解决嵌套数据的N+1查询问题
  3. 添加分页:针对可能无限增长的列表字段
  4. 优雅处理错误:返回用户友好的错误信息
  5. 验证输入:在数据库操作前的解析器层进行验证
  6. 使用上下文存储共享状态:数据库、认证信息、加载器等
  7. 实现授权:在解析器或指令层进行授权
  8. 积极缓存:对频繁访问的数据使用Redis或内存缓存
  9. 监控性能:跟踪查询复杂度和执行时间
  10. 使用@deprecated进行版本控制:永远不要破坏现有查询
  11. 全面测试:单元测试解析器,集成测试查询
  12. 文档化Schema:在SDL中使用描述信息
  13. 合理使用非空类型:仅用于真正必填的字段
  14. 组织Schema:按领域拆分为模块
  15. 生产环境安全:请求频率限制、查询白名单、深度限制

Resources and Tools

资源与工具

Essential Libraries

核心库

  • graphql-js: Core GraphQL implementation
  • express: Web server framework
  • graphql-http: HTTP handler for GraphQL
  • dataloader: Batching and caching
  • graphql-ws: WebSocket server for subscriptions
  • graphql-scalars: Common custom scalars
  • graphql-tools: Schema manipulation utilities
  • graphql-js:GraphQL核心实现
  • express:Web服务器框架
  • graphql-http:GraphQL的HTTP处理器
  • dataloader:批量处理与缓存
  • graphql-ws:用于订阅的WebSocket服务器
  • graphql-scalars:常用自定义标量
  • graphql-tools:Schema操作工具

Development Tools

开发工具

  • GraphiQL: In-browser GraphQL IDE
  • GraphQL Playground: Advanced GraphQL IDE
  • Apollo Studio: Schema registry and monitoring
  • GraphQL Code Generator: Generate TypeScript types
  • eslint-plugin-graphql: Lint GraphQL queries
  • GraphiQL:浏览器端GraphQL IDE
  • GraphQL Playground:高级GraphQL IDE
  • Apollo Studio:Schema注册与监控
  • GraphQL Code Generator:生成TypeScript类型
  • eslint-plugin-graphql:GraphQL查询代码检查

Learning Resources

学习资源


Skill Version: 1.0.0 Last Updated: October 2025 Skill Category: API Development, Backend, GraphQL, Web Development Compatible With: Node.js, Express, TypeScript, JavaScript

指南版本:1.0.0 最后更新:2025年10月 指南分类:API开发、后端开发、GraphQL、Web开发 兼容环境:Node.js、Express、TypeScript、JavaScript