📖 文章概述
REST 和 GraphQL 是当今主流的 API 设计模式。本文深入对比两者的设计理念、性能、开发体验和实战应用。
🎯 核心概念对比
REST API 简介
REST(Representational State Transfer)是一种基于 HTTP 的无状态架构风格。
REST 的核心原则:
- 资源导向:一切都是资源 (Resource)
- 方法导向:使用 HTTP 方法 (GET, POST, PUT, DELETE)
- 状态无关:每个请求都包含完整信息
- 缓存友好:充分利用 HTTP 缓存
示例:
GET /api/users # 获取用户列表
GET /api/users/1 # 获取用户 1
POST /api/users # 创建用户
PUT /api/users/1 # 更新用户 1
DELETE /api/users/1 # 删除用户 1
GET /api/users/1/posts # 获取用户 1 的文章
GraphQL 简介
GraphQL 是一种声明式的数据查询语言,允许客户端精确指定所需数据。
GraphQL 的核心原则:
- 类型系统:强类型的 Schema 定义
- 查询语言:客户端定义数据需求
- 单一端点:通常只有一个 /graphql
- 预测性:响应结构完全可预测
示例:
query {
user(id: 1) {
id
name
email
posts {
id
title
}
}
}
直观对比
| 特性 | REST API | GraphQL |
|---|---|---|
| 端点数量 | 多个 | 单个 |
| 数据获取 | 固定结构 | 灵活定制 |
| 过度获取 | 常见 | 不会 |
| 缺少数据 | 常见(N+1 问题) | 一次查询 |
| HTTP 缓存 | 友好 | 需特殊处理 |
| 实时性 | Polling/WebSocket | Subscription |
| 学习曲线 | 平缓 | 陡峭 |
| 工具生态 | 成熟 | 快速发展 |
🚀 REST API 详解
1. RESTful API 设计
// 标准 REST API 设计
// ✅ 好的 RESTful 设计
GET /api/v1/users // 列表
GET /api/v1/users/1 // 详情
POST /api/v1/users // 创建
PUT /api/v1/users/1 // 完整更新
PATCH /api/v1/users/1 // 部分更新
DELETE /api/v1/users/1 // 删除
// ✅ 嵌套资源
GET /api/v1/users/1/posts // 用户的文章
GET /api/v1/users/1/posts/5 // 特定文章
POST /api/v1/users/1/posts // 创建文章
// ❌ 不好的设计
GET /api/getUser?id=1 // 使用动词
GET /api/getUserPosts?id=1 // 复杂的查询
GET /api/users/1/getPosts // 混合风格
2. REST API 实现
// Node.js + Express 实现
import express from 'express'
const app = express()
// 获取用户列表
app.get('/api/v1/users', async (req, res) => {
const page = req.query.page || 1
const limit = req.query.limit || 10
const skip = (page - 1) * limit
try {
const users = await User.find()
.skip(skip)
.limit(limit)
.select('id name email') // 字段限制
const total = await User.countDocuments()
res.json({
data: users,
pagination: { page, limit, total }
})
} catch (error) {
res.status(500).json({ error: error.message })
}
})
// 获取用户和其所有文章
app.get('/api/v1/users/:id', async (req, res) => {
const { id } = req.params
try {
const user = await User.findById(id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
// ❌ N+1 问题:需要额外查询
const posts = await Post.find({ authorId: id })
res.json({
...user.toJSON(),
posts
})
} catch (error) {
res.status(500).json({ error: error.message })
}
})
// 创建用户
app.post('/api/v1/users', async (req, res) => {
const { name, email } = req.body
// 验证
if (!name || !email) {
return res.status(400).json({ error: 'Missing fields' })
}
try {
const user = new User({ name, email })
await user.save()
res.status(201).json(user)
} catch (error) {
res.status(500).json({ error: error.message })
}
})
// 更新用户
app.patch('/api/v1/users/:id', async (req, res) => {
const { id } = req.params
const updates = req.body
try {
const user = await User.findByIdAndUpdate(id, updates, {
new: true,
runValidators: true
})
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json(user)
} catch (error) {
res.status(500).json({ error: error.message })
}
})
// 删除用户
app.delete('/api/v1/users/:id', async (req, res) => {
const { id } = req.params
try {
const user = await User.findByIdAndDelete(id)
if (!user) {
return res.status(404).json({ error: 'User not found' })
}
res.json({ message: 'User deleted' })
} catch (error) {
res.status(500).json({ error: error.message })
}
})
📊 GraphQL 详解
3. GraphQL Schema 定义
# GraphQL Schema 定义
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
followers: [User!]!
createdAt: String!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
createdAt: String!
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
createdAt: String!
}
type Query {
# 查询用户
user(id: ID!): User
users(page: Int = 1, limit: Int = 10): [User!]!
# 查询文章
post(id: ID!): Post
posts(authorId: ID, limit: Int = 20): [Post!]!
# 搜索
search(query: String!): [SearchResult!]!
}
type Mutation {
# 创建用户
createUser(name: String!, email: String!): User!
# 更新用户
updateUser(id: ID!, name: String, email: String): User!
# 删除用户
deleteUser(id: ID!): Boolean!
# 创建文章
createPost(title: String!, content: String!, authorId: ID!): Post!
}
type Subscription {
# 用户创建时触发
userCreated: User!
# 新评论时触发
commentAdded(postId: ID!): Comment!
}
union SearchResult = User | Post | Comment
4. GraphQL 服务器实现
// Node.js + Apollo Server 实现
import { ApolloServer, gql } from 'apollo-server'
const typeDefs = gql`
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
users: [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(name: String!, email: String!): User!
updateUser(id: ID!, name: String): User!
deleteUser(id: ID!): Boolean!
createPost(title: String!, content: String!, authorId: ID!): Post!
}
`
// 解析器(Resolvers)
const resolvers = {
Query: {
user: async (_, { id }) => {
return await User.findById(id)
},
users: async () => {
return await User.find()
},
post: async (_, { id }) => {
return await Post.findById(id)
}
},
Mutation: {
createUser: async (_, { name, email }) => {
const user = new User({ name, email })
await user.save()
return user
},
updateUser: async (_, { id, name }) => {
return await User.findByIdAndUpdate(id, { name }, { new: true })
},
deleteUser: async (_, { id }) => {
const result = await User.findByIdAndDelete(id)
return !!result
},
createPost: async (_, { title, content, authorId }) => {
const post = new Post({ title, content, author: authorId })
await post.save()
return post
}
},
// 字段解析器(Field Resolvers)
User: {
posts: async (user) => {
// ✅ 自动解决 N+1 问题(使用 DataLoader)
return await Post.find({ author: user.id })
}
},
Post: {
author: async (post) => {
return await User.findById(post.author)
}
}
}
// 创建服务器
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
// 从请求中获取认证信息
const token = req.headers.authorization?.split('Bearer ')[1]
return { token }
}
})
server.listen(4000)
5. GraphQL 查询示例
# ✅ 精确查询:只获取需要的字段
query {
user(id: "1") {
name
email
posts {
title
createdAt
}
}
}
# ✅ 多个查询
query GetUserAndPosts {
user: user(id: "1") {
name
posts {
title
}
}
morePosts: posts(limit: 5) {
id
title
author {
name
}
}
}
# ✅ 使用变量
query GetUser($userId: ID!) {
user(id: $userId) {
name
email
}
}
# ✅ 片段复用
fragment UserInfo on User {
id
name
email
}
query {
user1: user(id: "1") {
...UserInfo
}
user2: user(id: "2") {
...UserInfo
}
}
📈 性能对比
6. 过度获取(Over-fetching)
REST API 的问题:
请求:GET /api/users/1
响应:
{
id: 1,
name: "John",
email: "john@example.com",
age: 30,
phone: "123-456-7890", // ❌ 不需要
address: "...", // ❌ 不需要
profilePic: "...", // ❌ 不需要
createdAt: "2023-01-01", // ❌ 不需要
updatedAt: "2023-12-07" // ❌ 不需要
}
成本:
- 网络传输:不必要的 1.2KB 数据
- 解析时间:浪费客户端资源
- 缓存污染:存储不需要的数据
GraphQL 的解决方案:
query {
user(id: 1) {
name
email
}
}
响应:
{
user: {
name: "John",
email: "john@example.com"
}
}
好处:
- 传输最小化:只有 48 字节
- 精确控制:客户端定义需求
- 带宽节省:特别在移动端
7. N+1 查询问题
REST API 的 N+1 问题:
请求 1: GET /api/users
响应: [
{ id: 1, name: "User1" },
{ id: 2, name: "User2" },
{ id: 3, name: "User3" }
]
如果需要每个用户的文章数:
请求 2: GET /api/users/1/posts ← N 个额外请求
请求 3: GET /api/users/2/posts
请求 4: GET /api/users/3/posts
总共 N+1 = 4 个请求!
GraphQL 的解决方案:
query {
users {
id
name
posts {
id
title
}
}
}
✅ 只需 1 个请求!
使用 DataLoader 优化数据库查询
8. 性能指标对比
| 场景 | REST | GraphQL | 胜者 |
|---|---|---|---|
| 简单查询 | 10ms | 15ms | REST |
| 复杂嵌套查询 | 200ms (N+1) | 50ms | GraphQL |
| 过度获取 | 500KB | 80KB | GraphQL |
| 缓存 | 简单 | 复杂 | REST |
| 平均场景 | 80ms | 45ms | GraphQL |
🎯 实战对比:用户列表场景
场景描述
获取用户列表,每个用户的基本信息和最新 3 篇文章。
REST API 实现
// 前端代码(多个请求)
async function getUsers() {
// 请求 1:获取用户列表
const usersRes = await fetch('/api/users?limit=10')
const users = await usersRes.json()
// 请求 2-11:获取每个用户的文章(N+1 问题)
const usersWithPosts = await Promise.all(
users.map(async (user) => {
const postsRes = await fetch(`/api/users/${user.id}/posts?limit=3`)
const posts = await postsRes.json()
return { ...user, posts }
})
)
return usersWithPosts
}
// 总请求数:11 个
// 总耗时:平均 200ms
// 数据大小:~500KB(包括不需要的字段)
GraphQL 实现
# 单个查询
query GetUsersWithPosts {
users(limit: 10) {
id
name
email
posts(limit: 3) {
id
title
createdAt
}
}
}
// 前端代码(单个请求)
const query = gql`
query GetUsersWithPosts {
users(limit: 10) {
id
name
email
posts(limit: 3) {
id
title
createdAt
}
}
}
`
const { data } = await apolloClient.query({ query })
对比结果
| 指标 | REST | GraphQL |
|---|---|---|
| 请求数 | 11 个 | 1 个 |
| 响应时间 | 200ms | 45ms |
| 传输数据 | 500KB | 80KB |
| 代码复杂度 | 高(处理 N+1) | 低(声明式) |
| 缓存管理 | 简单 | 需特殊处理 |
🛠️ 最佳实践
9. REST API 最佳实践
// ✅ DO
// 1. 使用版本号
GET /api/v1/users
// 2. 返回适当的 HTTP 状态码
POST /api/users → 201 Created
PUT /api/users/1 → 200 OK
DELETE /api/users/1 → 204 No Content
// 3. 使用分页
GET /api/users?page=1&limit=20
// 4. 使用过滤和排序
GET /api/users?role=admin&sort=-createdAt
// 5. 返回标准化结构
{
data: [...],
meta: { total: 100, page: 1 },
errors: []
}
// ❌ DON'T
// 1. 不要用动词作为端点
GET /api/getUsers
GET /api/createUser
// 2. 不要返回不一致的状态码
DELETE /api/users/1 → 200 OK(应该 204)
// 3. 不要忽视分页
GET /api/users // 返回所有用户?
// 4. 不要过度返回数据
// 应该支持字段选择
10. GraphQL 最佳实践
# ✅ DO
# 1. 使用 ID 类型
type User {
id: ID!
name: String!
}
# 2. 非空字段明确标记
type Post {
id: ID!
title: String! # 必须
description: String # 可选
}
# 3. 使用有意义的错误
type Mutation {
createUser(name: String!): CreateUserPayload!
}
type CreateUserPayload {
user: User
error: Error
success: Boolean!
}
# 4. 使用 DataLoader 解决 N+1
# 实现在 Resolver 中
# 5. 实现分页
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
}
# ❌ DON'T
# 1. 不要使用默认参数
args: (limit: Int = 100) # 太大了
# 2. 不要返回列表
type Mutation {
deleteUsers(ids: [ID!]!): [User!]! # 错误
}
# 3. 不要忽视错误处理
type Mutation {
createUser(name: String!): User! # 如果失败呢?
}
# 4. 不要返回过深的嵌套
query {
user {
posts {
comments {
author {
posts {
# 太深了
}
}
}
}
}
}
🎓 选择指南
何时选择 REST?
✅ 选择 REST 当:
├─ 需要简单的 CRUD 操作
├─ 客户端需求比较固定
├─ 需要好的 HTTP 缓存支持
├─ 团队熟悉 REST 模式
├─ 需要最佳的浏览器兼容性
└─ CDN 支持很重要
示例项目:
- 简单的博客系统
- 公开的内容 API
- 移动端,要求强缓存
何时选择 GraphQL?
✅ 选择 GraphQL 当:
├─ 有多种不同的客户端(Web、移动、TV)
├─ 每个客户端需求不同
├─ 避免 N+1 查询问题
├─ 需要实时数据订阅
├─ 需要自动文档化
└─ 团队愿意学习新技术
示例项目:
- 复杂的 SaaS 应用
- 多端应用(Web + 移动 + 桌面)
- 需要频繁 API 演进
- 实时协作应用
混合方案
实际上,很多企业采用混合方案:
┌─────────────────────┐
│ GraphQL 网关 │ (聚合层)
├─────────────────────┤
│ REST API + gRPC API │ (微服务层)
└─────────────────────┘
优点:
- 保留现有 REST 投资
- 获得 GraphQL 的灵活性
- 逐步迁移
- 支持多种客户端
📚 扩展资源
总结
REST vs GraphQL 对比总结:
| 维度 | REST | GraphQL |
|---|---|---|
| 学习 | 简单 | 复杂 |
| 性能 | 中等 | 优秀 |
| 灵活性 | 低 | 高 |
| 缓存 | 简单 | 需特殊处理 |
| 生态 | 成熟 | 快速发展 |
| 最佳场景 | 简单 CRUD | 复杂查询 |
建议:
- 小项目:选择 REST
- 复杂项目:选择 GraphQL
- 大企业:使用混合方案
选择正确的 API 设计范式,是构建高效应用的第一步!