GraphQL vs REST
从 API 边界、取数粒度、HTTP 语义、缓存、演进、权限与复杂度归属比较 GraphQL 与 REST,避免把两者理解成简单的优劣替代关系。
[!info] related notes
- 所属 MOC: graphql-moc
- 相关概念:
- 易混淆概念:
- 相关资源:
GraphQL vs REST
比较范围
GraphQL 和 REST 都可以承载前后端 API,但它们把复杂度放在不同位置。
最重要的问题不是“哪个更先进”,而是:由谁决定返回数据形状,以及系统愿意在哪里承担复杂度。
核心差异
| 维度 | REST | GraphQL |
|---|---|---|
| API 入口 | 多个资源 endpoint | 常见单一 GraphQL endpoint |
| 数据形状 | 服务端 DTO 决定 | 客户端在 Schema 范围内选字段 |
| 操作语义 | GET/POST/PATCH/DELETE 等 HTTP 方法 | Query/Mutation/Subscription |
| 契约 | URL + method + DTO/OpenAPI | 强类型 Schema |
| 数据组合 | 常由 endpoint/BFF 聚合 | Query 天生可组合字段关系 |
| 客户端缓存 | URL/query key 很自然 | 常依赖 entity normalization |
| 性能风险 | endpoint 粒度设计不合适 | 深查询、N+1、查询复杂度 |
Over-fetching 与 Under-fetching
REST:
GET /users/u1
如果返回 30 个字段,而页面只需要 name,可能 over-fetch。
如果页面同时需要 user、plan、diagnosis:
GET /users/u1
GET /users/u1/plan
GET /users/u1/diagnosis
可能需要多个 round trip。
GraphQL 允许一次声明:
query {
user(id: "u1") {
name
plan { title }
diagnosis { summary }
}
}
但“一个 HTTP 请求”不意味着“一个数据库查询”;resolver 设计不好反而可能制造大量 SQL。
HTTP 语义
REST 天然利用 HTTP:
GET /users/u1
404 Not Found
ETag / Cache-Control
GraphQL 常把不同业务操作装进同一个 HTTP endpoint,因此 transport status 与业务/字段错误不能简单一一对应。
这让 GraphQL API 更统一,但也要求客户端理解 GraphQL 自己的 data + errors 语义。
缓存差异
REST 很容易以请求 URL / query key 为缓存身份。
GraphQL 一个 Query 可以返回多个实体,同一个 User:1 又可能出现在许多查询中,因此 Apollo 一类客户端倾向把结果拆成实体缓存:
User:1
Diagnosis:d9
TrainingPlan:p3
这就是 normalized cache 的来源。
演进方式
REST 常通过:
/v1 → /v2
或保持 DTO 向后兼容。
GraphQL 更倾向:
新增字段
旧字段 @deprecated
逐步迁移客户端
Schema 因而更像一个长期演进的共享合同。
选择规则
优先考虑 GraphQL,当:
- 页面数据组合复杂;
- 多客户端需要不同字段组合;
- 强类型 Schema 与前端 tooling 能产生明显收益;
- 团队能治理 Resolver、Cache、授权和查询成本。
优先考虑 REST,当:
- API 资源边界稳定直接;
- CRUD/命令语义简单;
- HTTP 缓存、状态码、CDN 等语义很重要;
- 不需要客户端自由组合数据。
BodySense 的判断
学习 GraphQL 不等于要迁移 BodySense。
当前 REST + TanStack Query + SSE 本身是一套合理组合。GraphQL Lab 的价值是让你理解另一种 API contract/cache/resolver 模型,然后能够回答:换过去究竟解决什么问题,又新增什么成本。