API 集成入门:REST vs GraphQL 对比与选型指南
无论你是前端开发者还是后端开发者,API 都是日常工作中不可或缺的部分。REST 多年来一直是 API 设计的标准,而 GraphQL 作为一种更灵活的新范式正在快速普及。
举个具体的例子:假设你在做一个内容管理后台,用户详情页要同时展示用户资料、他最近发布的 5 篇文章、每篇文章的前 3 条评论。用 REST 可能要发 7 次请求、再在前端手动拼装;用 GraphQL 一次查询就能拿到完整结构。这种“数据获取方式”的差异会直接体现在页面加载时间和前端代码复杂度上。下面先从两种范式的基础讲起。
一、REST 基础
1.1 什么是 REST
REST(Representational State Transfer)是一种基于 HTTP 协议的 API 设计风格。核心思想是将资源抽象为 URL,通过 HTTP 方法进行操作。
# RESTful API 设计
GET /api/users # 获取用户列表
GET /api/users/1 # 获取单个用户
POST /api/users # 创建用户
PUT /api/users/1 # 更新用户
DELETE /api/users/1 # 删除用户
1.2 REST 请求示例
// 获取用户及其文章
const user = await fetch('/api/users/1').then(r => r.json());
const posts = await fetch('/api/users/1/posts').then(r => r.json());
const comments = await fetch('/api/posts/1/comments').then(r => r.json());
// 响应通常包含固定结构
// GET /api/users/1
{
"id": 1,
"name": "张三",
"email": "[email protected]",
"avatar": "https://..."
}
1.3 REST 状态码与语义
REST 的规范程度体现在 HTTP 状态码上,语义清晰的接口应当善用它们:
| 状态码 | 含义 | 典型场景 |
|---|---|---|
| 200 OK | 成功 | 查询返回资源 |
| 201 Created | 创建成功 | POST 新建后返回 Location |
| 204 No Content | 成功但无返回体 | DELETE 删除成功 |
| 400 Bad Request | 参数错误 | 校验失败 |
| 401 / 403 | 未认证 / 无权限 | 登录失效、越权访问 |
| 404 Not Found | 资源不存在 | 无效 ID |
| 429 Too Many Requests | 触发限流 | 频控生效 |
| 500 / 502 / 503 | 服务端异常 | 代码错误、上游故障 |
很多团队的 REST 接口“长得很 REST”,却一律返回 200 加业务码,等于丢掉了 HTTP 自带的语义,也丢掉了中间层(CDN、网关、代理)基于状态码做缓存与重试的能力。
二、GraphQL 基础
2.1 什么是 GraphQL
GraphQL 是由 Facebook 开发的一种 API 查询语言。客户端可以精确指定需要的数据,不多不少。
# GraphQL 查询
query {
user(id: 1) {
name
email
posts {
title
comments {
content
author {
name
}
}
}
}
}
2.2 GraphQL 响应
{
"data": {
"user": {
"name": "张三",
"email": "[email protected]",
"posts": [
{
"title": "第一篇文章",
"comments": [
{
"content": "写得好!",
"author": {
"name": "李四"
}
}
]
}
]
}
}
}
2.3 变更(Mutation)与订阅
GraphQL 用 query 读数据,用 mutation 写数据,两者语法一致:
mutation {
createPost(input: { title: "你好,GraphQL", authorId: 1 }) {
id
title
createdAt
}
}
subscription 用于推送类场景(实时评论、在线状态),这是 REST 需要自己用 WebSocket 约定的能力。需要提醒的是,GraphQL 的写入同样要做鉴权与参数校验——它解决的是“取数灵活”,不是“安全豁免”。
三、核心差异对比
3.1 数据获取
| 维度 | REST | GraphQL |
|---|---|---|
| 数据量 | 固定结构,可能过多或不足 | 客户端精确指定 |
| 请求次数 | 可能需要多次请求 | 一次请求获取所有数据 |
| 嵌套数据 | 需多次请求或自定义端点 | 直接在查询中嵌套 |
3.2 示例对比
场景:获取用户信息 + 最新的 5 篇文章 + 每篇文章的前 3 条评论
REST 方式:
// 3 次 API 请求
const user = await fetch('/api/users/1');
const posts = await fetch('/api/users/1/posts?limit=5');
const comments = await Promise.all(
posts.map(p => fetch(`/api/posts/${p.id}/comments?limit=3`))
);
// 或者后端专门创建一个聚合端点
GraphQL 方式:
query {
user(id: 1) {
name
email
posts(last: 5) {
title
comments(last: 3) {
content
author { name }
}
}
}
}
// 一次请求获得所有数据
3.3 性能对比
| 特性 | REST | GraphQL |
|---|---|---|
| 缓存 | HTTP 缓存天然支持 | 需要额外缓存方案 |
| 网络请求数 | 可能较多 | 通常较少 |
| 载荷大小 | 可能包含不需要的数据 | 客户端精确指定 |
| 服务器性能 | 可控(预定义的数据集) | 需防范复杂查询 |
3.4 缓存策略
REST 可以免费搭上 HTTP 缓存:给 GET 响应加 Cache-Control: max-age=3600、ETag、Last-Modified,CDN 和浏览器就能自动缓存,图片和静态接口的命中率很高。GraphQL 默认所有请求都走同一个 /graphql POST 端点,无法直接命中 HTTP 缓存,需要额外方案:Apollo 的自动持久化查询(APQ)把查询字符串换成哈希 ID,配合 CDN 缓存 GET 请求;服务端也可引入 DataLoader 做请求级去重与缓存。对以读为主的业务(商品详情、文章页),REST 在缓存上仍占明显优势。
3.5 版本管理
REST 常见 /api/v1/users、/api/v2/users 这样的显式版本号,向后不兼容的改动靠升版本号隔离。GraphQL 没有 URL 版本概念,官方思路是增量演进:只加字段、不改语义,废弃字段用 @deprecated 标注并逐步下线。好处是不用维护多套端点;代价是团队要有良好的 schema 评审习惯,否则查询里塞满废弃字段,语义会越来越难维护。
四、服务端实现
REST 实现(Node.js + Express)
// REST 路由
const express = require('express');
const app = express();
app.get('/api/users/:id', async (req, res) => {
const user = await db.findUser(req.params.id);
res.json(user);
});
app.get('/api/users/:id/posts', async (req, res) => {
const posts = await db.findPostsByUser(req.params.id);
res.json(posts);
});
GraphQL 实现(Node.js + Apollo Server)
const { ApolloServer, gql } = require('apollo-server');
// 类型定义
const typeDefs = gql`
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
comments: [Comment!]!
}
type Query {
user(id: ID!): User
}
`;
// 解析器
const resolvers = {
Query: {
user: async (_, { id }) => db.findUser(id),
},
User: {
posts: async (user) => db.findPostsByUser(user.id),
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen(4000);
五、选型建议
选 REST 的场景
- 简单 CRUD 应用:数据模型简单,不需要嵌套查询
- 缓存是关键需求:REST 的 HTTP 缓存机制成熟
- 微服务架构:服务间 REST 接口更加直观
- 文件上传/下载:处理二进制数据更自然
- 团队对 REST 更熟悉:降低学习成本
选 GraphQL 的场景
- 复杂的前端数据需求:不同页面需要不同数据集
- 多个前端客户端:Web + Mobile 需要不同数据
- API 需要快速迭代:前端需求变更时后端不需要改接口
- 数据有复杂的嵌套关系:一次查询获取关联数据
一个真实的选型案例
朋友做的一个跨境电商后台,最初用 REST。随着运营要的报表维度越来越多,后端每隔几周就要加一个聚合端点,前端还要串好几次请求,维护成本直线上升。后来团队把“面向运营后台的查询”迁到 GraphQL,对外公开的订单、商品接口仍保留 REST,半年下来新增查询需求基本不用动后端。这个案例的结论很朴素:对外、重缓存的接口用 REST,对内、重取数的界面用 GraphQL,比“全站统一一种”更现实。
六、混合方案
很多团队采用 REST + GraphQL 混合方案:
方案一:GraphQL 作为 API Gateway
GraphQL Gateway → REST 服务 A / REST 服务 B / REST 服务 C
前端只与 GraphQL 交互,后端服务之间用 REST
方案二:一部分用 REST,一部分用 GraphQL
公开 API → REST(缓存友好)
内部前端 → GraphQL(灵活取数)
七、总结
REST 和 GraphQL 不是非此即彼的选择。REST 胜在简洁、缓存友好和生态成熟;GraphQL 胜在灵活、高效和类型安全。对于新项目,可以考虑从 GraphQL 开始(特别是前后端分离的 SPA 应用);对于已有 REST API,可以逐步引入 GraphQL,没有迁移压力。技术选型的核心是匹配业务需求,而不是追逐热门技术。
常见误区
- “GraphQL 一定更快”:不一定。GraphQL 把“少请求”做得好,但 resolver 写得不讲究时,一条深嵌套查询可能触发几十次数据库查询,比 REST 更慢,配合 DataLoader 和查询深度限制才是关键。
- “REST 不能做复杂查询”:可以,用查询参数、过滤器和专用聚合端点都能实现,只是每次需求变化都要改后端。
- “用了 GraphQL 就不用写文档”:恰恰相反,schema 就是文档,但要维护得好;GraphQL 的 introspection 能自动生成可交互文档(如 GraphiQL)。
参考:MDN 关于 HTTP 方法 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods ;GraphQL 官方文档 https://graphql.org/learn/ ;Apollo 持久化查询 https://www.apollographql.com/docs/apollo-server/performance/apq/