GraphQL 入门与实践
什么是 GraphQL
GraphQL 是由 Facebook 开发的 API 查询语言和运行时。它允许客户端精确请求所需的数据,避免了 REST 中的 over-fetching 和 under-fetching 问题。
核心概念
Schema 定义
GraphQL 使用 Schema Definition Language (SDL) 定义 API 的数据类型和操作:
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
createdAt: String!
}
type Query {
user(id: ID!): User
users(page: Int, limit: Int): [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String): User!
deleteUser(id: ID!): Boolean!
}
Query - 查询数据
客户端精确指定所需字段:
query GetUserWithPosts {
user(id: "1") {
name
email
posts {
title
createdAt
}
}
}
Mutation - 修改数据
mutation CreateNewUser {
createUser(name: "张三", email: "zhangsan@example.com") {
id
name
email
}
}
Subscription - 实时订阅
subscription OnNewPost {
newPost {
id
title
author {
name
}
}
}
Resolver 实现
Resolver 是 schema 中每个字段的实际数据获取函数:
const resolvers = {
Query: {
user: async (_, { id }) => {
return await db.users.findById(id);
},
users: async (_, { page = 1, limit = 10 }) => {
return await db.users.findAll({ offset: (page - 1) * limit, limit });
},
},
User: {
posts: async (parent) => {
return await db.posts.findAll({ where: { authorId: parent.id } });
},
},
};
N+1 问题
GraphQL 的嵌套查询容易引发 N+1 查询问题。使用 DataLoader 批量加载数据:
const DataLoader = require('dataloader');
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findAllByIds(ids);
return ids.map(id => users.find(user => user.id === id));
});
const resolvers = {
Post: {
author: async (post) => {
return await userLoader.load(post.authorId);
},
},
};
与 REST 的对比
| 特性 | REST | GraphQL |
|---|---|---|
| 数据获取 | 服务端定义返回结构 | 客户端精确指定 |
| 版本管理 | URL 版本 (v1, v2) | 通过新增字段演进 |
| 缓存 | HTTP 缓存天然支持 | 需要额外方案 |
| 学习曲线 | 低 | 中等 |
| 工具生态 | 成熟 | 快速发展 |
| 适用场景 | 资源型 CRUD | 复杂数据聚合 |
常用工具
- Apollo Server:功能完整的 Node.js GraphQL 服务端
- Apollo Client:前端 GraphQL 客户端,支持缓存和状态管理
- GraphQL Code Generator:从 schema 自动生成 TypeScript 类型
- GraphiQL:交互式 GraphQL IDE