目录
GraphQL 实战指南:从设计到优化
作者:文文
GraphQL vs REST:核心区别
| 维度 | REST | GraphQL |
|---|---|---|
| 数据获取 | 固定字段 | 客户端选择字段 |
| 多个资源 | 多次请求 | 单次请求 |
| 过度获取 | 常见 | 避免 |
| 过度查询 | 需要特殊 API | 单个端点 |
| 缓存 | HTTP 缓存 | 需要自定义 |
| 学习曲线 | 简单 | 相对复杂 |
GraphQL 基础
定义 Schema
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User]
post(id: ID!): Post
}
type User {
id: ID!
name: String!
email: String!
posts: [Post]
}
type Post {
id: ID!
title: String!
content: String!
author: User!
}
type Mutation {
createUser(name: String!, email: String!): User
createPost(title: String!, content: String!, authorId: ID!): Post
}
查询示例
# 查询用户及其文章
query {
user(id: "1") {
id
name
email
posts {
id
title
}
}
}
# 变更:创建用户
mutation {
createUser(name: "Alice", email: "alice@example.com") {
id
name
email
}
}
Node.js 实现(Apollo Server)
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
type Query {
hello: String
user(id: ID!): User
}
type User {
id: ID!
name: String!
email: String!
}
`;
const resolvers = {
Query: {
hello: () => "Hello World!",
user: (_, { id }) => {
// 从数据库查询
return database.users.find(u => u.id === id);
}
}
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(4000);
N+1 查询问题解决
问题:N+1 查询
// 不好:会发送 1 + N 个数据库查询
const resolvers = {
Query: {
users: () => database.query('SELECT * FROM users')
},
User: {
posts: (user) => database.query(`SELECT * FROM posts WHERE user_id=${user.id}`)
}
};
// 如果查询了 100 个用户及其文章,就需要 1 + 100 = 101 个查询!
解决方案:DataLoader
const DataLoader = require('dataloader');
// 批量加载 posts
const postLoader = new DataLoader(async (userIds) => {
const posts = await database.query(
`SELECT * FROM posts WHERE user_id IN (${userIds.join(',')})`
);
return userIds.map(id => posts.filter(p => p.user_id === id));
});
const resolvers = {
User: {
posts: (user) => postLoader.load(user.id)
}
};
缓存策略
1. HTTP 缓存(客户端)
// Apollo Client
const cache = new InMemoryCache({
typePolicies: {
User: {
keyFields: ["id"],
fields: {
posts: {
merge(existing = [], incoming) {
return [...existing, ...incoming];
}
}
}
}
}
});
2. 服务器缓存
const redis = require('redis').createClient();
const resolvers = {
Query: {
user: async (_, { id }) => {
const cacheKey = `user:${id}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await database.users.findById(id);
await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600);
return user;
}
}
};
性能优化建议
- 字段级权限控制:避免暴露敏感数据
- 查询深度限制:防止深嵌套查询
- 查询复杂度分析:限制复杂查询
- 批处理和缓存:使用 DataLoader
- 错误处理:提供有意义的错误信息
- 监控和日志:跟踪查询性能
文文,2026 年 3 月 16 日
