GraphQL 常被宣传为 REST 的终结者,但实际两者各有适合场景。盲目选择 GraphQL 会带来 N+1 查询、复杂度爆炸、缓存失效等坑;只用 REST 又有过取/欠取问题。理性对比再做选择。
典型架构与复杂度防护
GraphQL 单端点接收查询,前端声明式指定字段结构,解决 REST 常见的 20 个接口拼数据或大接口返回 80% 不用字段的问题。但必须在网关层加上查询复杂度分析、字段级权限、深度限制。
import express, { Request, Response } from "express";
import { graphqlHTTP } from "express-graphql";
import { buildSchema, parse, visit, Kind } from "graphql";
const app = express();
app.use(express.json());
const schema = buildSchema(`
type User { id: ID!; username: String!; orders(limit: Int = 10): [Order!]! }
type Order { id: ID!; total: Float!; items(limit: Int = 20): [OrderItem!]! }
type OrderItem { productId: ID!; qty: Int!; price: Float! }
type Query { user(id: ID!): User; users(limit: Int = 20): [User!]! }
schema { query: Query }
`);
class ComplexityGuard {
private costs: Record<string, number> = { orders: 5, items: 2, users: 10 };
private readonly MAX = 150;
analyze(query: string): { total: number; exceeded: boolean } {
const doc = parse(query); let total = 1;
visit(doc, {
Field: (node) => {
const base = this.costs[node.name.value] || 1;
const args = Object.fromEntries((node.arguments || []).map(a => [a.name.value, (a.value as any).value]));
const mult = args.limit ? Math.ceil(Number(args.limit) / 10) : 1;
total += base * mult;
},
});
return { total, exceeded: total > this.MAX };
}
}
const root = {
user: ({ id }: { id: string }) => ({
id, username: `u${id}`,
orders: ({ limit = 10 }: any) => Array.from({ length: limit }, (_, i) => ({
id: `o${i}`, total: 99.5 + i,
items: ({ limit = 20 }: any) => Array.from({ length: limit }, (_, k) => ({ productId: `p${k}`, qty: k + 1, price: 9.9 })),
})),
}),
users: ({ limit = 20 }: any) => Array.from({ length: limit }, (_, i) => root.user({ id: String(i + 1) })),
};
const guard = new ComplexityGuard();
app.get("/rest/users/:id", (req: Request, res: Response) => {
res.json({ data: { id: req.params.id, username: `u${req.params.id}` }, _links: { orders: { href: `/rest/users/${req.params.id}/orders` } } });
});
app.use("/graphql", (req: Request, res: Response, next) => {
const q = (req.body?.query) || (req.query.query as string) || "";
const r = guard.analyze(q);
if (r.exceeded) return res.status(400).json({ error: `Complexity ${r.total} exceeds cap 150` });
res.setHeader("X-GraphQL-Complexity", String(r.total));
next();
}, graphqlHTTP({ schema, rootValue: root, graphiql: process.env.NODE_ENV !== "production" }));
app.listen(3000, () => console.log("[compare] REST+GraphQL on :3000"));
七大维度横向对比
| 对比维度 | REST (JSON:API风格) | GraphQL | 胜者 |
|---|---|---|---|
| 网络传输效率 | 多请求过取欠取 | 单请求精确字段POST不利CDN | 简单REST赢/复杂视图GraphQL赢 |
| 前端开发体验 | 新字段等后端加接口 | 自助取字段+内省生成类型 | GraphQL |
| 后端演进成本 | 版本化管理/v1/v2 | 字段弃用平滑但resolver复杂度涨 | REST |
| HTTP缓存 | 天然支持Get请求 | 需持久化查询ID+自定义层 | REST |
| 类型安全 | 需OpenAPI代码生成 | Schema强类型+内省生成SDK | GraphQL |
| 安全风险 | 普通CRUD鉴权易做 | 深度/复杂度/批注入要防护 | REST安全面更小 |
| 生态工具链 | OpenAPI/Postman成熟 | Apollo Studio/GraphiQL完善 | 打平 |
最佳实践
决策树:团队以 BFF 形态存在且前端迭代快 → GraphQL;微服务间调用或纯资源 CRUD → REST;混合架构:BFF 用 GraphQL 组合多个 REST 微服务是目前大厂主流方案。