REST vs GraphQL
REST API 的经典问题:获取用户列表时,/users 返回所有字段(过度获取);获取用户及其文章时,需要调用 /users/1 和 /users/1/posts(多次请求)。GraphQL 让客户端精确指定所需数据:
query {
user(id: 1) {
name
posts(limit: 5) {
title
createdAt
}
}
}
Schema 设计与类型系统
GraphQL Schema 是强类型的 API 契约:
type User {
id: ID!
name: String!
email: String
posts(limit: Int = 10): [Post!]!
}
type Post {
id: ID!
title: String!
content: String
author: User!
createdAt: String!
}
type Query {
user(id: ID!): User
posts(page: Int = 1): [Post!]!
}
Apollo Server 搭建
const { ApolloServer } = require("apollo-server");
const typeDefs = fs.readFileSync("./schema.graphql", "utf8");
const resolvers = {
Query: {
user: (_, { id }) => db.users.findById(id),
},
User: {
posts: (user, { limit }) => db.posts.findByAuthor(user.id, limit),
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(4000).then(({ url }) => console.log(`🚀 ${url}`));
N+1 问题与 DataLoader
GraphQL 最常见的性能陷阱——获取100个用户,每个触发一次posts查询(100+1次)。DataLoader 通过批处理解决:
const DataLoader = require("dataloader");
const postLoader = new DataLoader(async (userIds) => {
const posts = await db.posts.findByAuthors(userIds);
return userIds.map(id => posts.filter(p => p.authorId === id));
});
生产环境优化
- 使用 Persisted Queries 防止恶意复杂查询
- 设置查询深度限制和复杂度评分
- 利用 CDN 缓存 GET 请求的查询结果
- 按类型拆分 Schema 文件,保持可维护性