GraphQL
欢迎来到 GraphQL 知识库!
⚡ GraphQL 简介
GraphQL 是一种用于 API 的查询语言,由 Facebook 于 2012 年开发并在 2015 年开源。它提供了一种更高效、强大和灵活的替代 REST 的方案。
🎯 GraphQL vs REST
| 特性 | GraphQL | REST |
|---|---|---|
| 获取数据 | 一次请求获取多个资源 | 多次请求多个端点 |
| 数据获取 | 精确获取需要的字段 | 获取整个资源 |
| 版本管理 | 无需版本,字段可废弃 | 需要 v1、v2 版本 |
| 开发体验 | 强类型,自动文档 | 需手动维护文档 |
| 学习曲线 | 较陡 | 较平缓 |
| 缓存 | 复杂 | 简单(HTTP 缓存) |
✅ GraphQL 优势
1. 避免过度获取和不足获取
REST 问题:
javascript
// 只需要用户名和邮箱,却获取了所有字段
GET /users/123
{
"id": 123,
"username": "zhangsan",
"email": "zhang@example.com",
"age": 25,
"address": {...},
"profile": {...}
// ... 很多不需要的字段
}
// 需要用户信息和他的文章,要请求两次
GET /users/123
GET /users/123/postsGraphQL 解决:
graphql
# 一次请求,精确获取需要的数据
query {
user(id: 123) {
username
email
posts {
title
createdAt
}
}
}2. 强类型系统
GraphQL 使用 Schema 定义 API,提供类型检查和自动文档。
3. 实时更新
通过 Subscription 支持实时数据推送。
4. 单一端点
所有请求都发送到同一个端点,简化路由。
🎯 核心概念
1. Schema(模式)
定义 API 的类型系统。
graphql
# 定义类型
type User {
id: ID! # ! 表示必填
username: String!
email: String
age: Int
posts: [Post!]! # 数组类型
}
type Post {
id: ID!
title: String!
content: String
author: User!
createdAt: String
}
# 定义查询
type Query {
user(id: ID!): User
users: [User!]!
post(id: ID!): Post
}
# 定义变更
type Mutation {
createUser(username: String!, email: String!): User
updateUser(id: ID!, username: String): User
deleteUser(id: ID!): Boolean
}
# 定义订阅
type Subscription {
userCreated: User
postAdded: Post
}2. Query(查询)
读取数据。
graphql
# 基本查询
query {
users {
id
username
email
}
}
# 带参数查询
query {
user(id: "123") {
username
email
posts {
title
}
}
}
# 使用变量
query GetUser($id: ID!) {
user(id: $id) {
username
email
}
}
# 别名
query {
user1: user(id: "123") {
username
}
user2: user(id: "456") {
username
}
}
# 片段
fragment UserInfo on User {
username
email
}
query {
user(id: "123") {
...UserInfo
posts {
title
}
}
}3. Mutation(变更)
修改数据。
graphql
# 创建用户
mutation {
createUser(username: "lisi", email: "li@example.com") {
id
username
email
}
}
# 使用变量
mutation CreateUser($username: String!, $email: String!) {
createUser(username: $username, email: $email) {
id
username
email
}
}
# 多个变更
mutation {
createUser(username: "lisi", email: "li@example.com") {
id
}
createPost(title: "标题", authorId: "123") {
id
title
}
}4. Subscription(订阅)
实时数据推送。
graphql
subscription {
userCreated {
id
username
email
}
}🚀 快速开始
Node.js + Apollo Server
javascript
const { ApolloServer, gql } = require('apollo-server');
// 1. 定义 Schema
const typeDefs = gql`
type User {
id: ID!
username: String!
email: String
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(username: String!, email: String!): User
}
`;
// 2. 模拟数据
let users = [
{ id: '1', username: 'zhangsan', email: 'zhang@example.com' },
{ id: '2', username: 'lisi', email: 'li@example.com' }
];
// 3. 定义 Resolver
const resolvers = {
Query: {
users: () => users,
user: (parent, args) => users.find(u => u.id === args.id)
},
Mutation: {
createUser: (parent, args) => {
const user = {
id: String(users.length + 1),
username: args.username,
email: args.email
};
users.push(user);
return user;
}
}
};
// 4. 创建服务器
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});查询示例
graphql
# 查询所有用户
query {
users {
id
username
email
}
}
# 查询单个用户
query {
user(id: "1") {
username
email
}
}
# 创建用户
mutation {
createUser(username: "wangwu", email: "wang@example.com") {
id
username
email
}
}🔗 关联查询
graphql
# Schema 定义
type User {
id: ID!
username: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
author: User!
}
type Query {
user(id: ID!): User
}javascript
// Resolver 实现
const resolvers = {
Query: {
user: (parent, args) => {
return db.users.findById(args.id);
}
},
User: {
// 解析 user.posts 字段
posts: (parent) => {
return db.posts.findByAuthorId(parent.id);
}
},
Post: {
// 解析 post.author 字段
author: (parent) => {
return db.users.findById(parent.authorId);
}
}
};graphql
# 查询用户及其文章
query {
user(id: "123") {
username
email
posts {
title
createdAt
}
}
}🎯 DataLoader 解决 N+1 问题
N+1 问题:查询一个用户列表及其文章时,会产生 N+1 次数据库查询。
javascript
const DataLoader = require('dataloader');
// 批量加载用户
const userLoader = new DataLoader(async (userIds) => {
const users = await db.users.findByIds(userIds);
return userIds.map(id => users.find(u => u.id === id));
});
// Resolver 中使用
const resolvers = {
Post: {
author: (parent) => {
return userLoader.load(parent.authorId); // 批量加载
}
}
};🔒 认证与授权
javascript
const { ApolloServer } = require('apollo-server');
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
// 从 header 获取 token
const token = req.headers.authorization || '';
const user = getUserFromToken(token);
return { user };
}
});
// Resolver 中检查权限
const resolvers = {
Query: {
me: (parent, args, context) => {
if (!context.user) {
throw new Error('未登录');
}
return context.user;
}
},
Mutation: {
createPost: (parent, args, context) => {
if (!context.user) {
throw new Error('未登录');
}
// 创建文章
}
}
};📊 分页
基于偏移量
graphql
type Query {
users(offset: Int, limit: Int): UserConnection!
}
type UserConnection {
nodes: [User!]!
totalCount: Int!
}Relay 风格(推荐)
graphql
type Query {
users(first: Int, after: String): UserConnection!
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
}
type UserEdge {
node: User!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}🔍 错误处理
javascript
const { ApolloError } = require('apollo-server');
const resolvers = {
Query: {
user: (parent, args) => {
const user = db.users.findById(args.id);
if (!user) {
throw new ApolloError('用户不存在', 'USER_NOT_FOUND');
}
return user;
}
}
};响应:
json
{
"errors": [
{
"message": "用户不存在",
"extensions": {
"code": "USER_NOT_FOUND"
}
}
]
}💡 最佳实践
合理设计 Schema
- 遵循命名规范
- 使用描述性名称
- 合理使用必填(!)
避免 N+1 问题
- 使用 DataLoader
- 批量查询
字段级别权限
- 在 Resolver 中检查权限
- 隐藏敏感字段
查询复杂度限制
- 限制查询深度
- 限制查询复杂度
缓存策略
- HTTP 缓存
- Apollo Client 缓存
- Redis 缓存
错误处理
- 统一错误格式
- 提供有意义的错误信息
文档化
- 使用描述字段
- 保持 Schema 清晰
📖 学习资源
官方资源
工具推荐
- GraphQL Playground - 交互式查询工具
- Apollo Studio - API 管理平台
- GraphQL Voyager - Schema 可视化
推荐教程
准备好了吗?开始你的 GraphQL 学习之旅!