API 设计最佳实践:REST vs GraphQL vs gRPC
设计 API 是现代开发的必修课。但选什么风格、怎么设计接口、如何处理错误,每个团队都有自己的经验。
这篇文章总结了我多年实战中沉淀下来的 API 设计经验,不分风格,只讲通用的最佳实践。
三种主流 API 风格
| 特性 | REST | GraphQL | gRPC |
|------|------|---------|------|
| 传输协议 | HTTP/1.1+ | HTTP | HTTP/2 |
| 数据格式 | JSON/XML | JSON | Protocol Buffers |
| 查询灵活性 | 固定结构 | 按需查询 | 预定义 |
| 缓存支持 | 原生 HTTP 缓存 | 需额外配置 | 需额外配置 |
| 学习成本 | 低 | 中 | 高 |
| 浏览器支持 | 原生 | 原生 | 需 gRPC-Web |
REST API 设计
URL 设计
javascript
// ✅ 好的设计
GET /api/users // 获取用户列表
GET /api/users/:id // 获取单个用户
POST /api/users // 创建用户
PUT /api/users/:id // 全量更新
PATCH /api/users/:id // 部分更新
DELETE /api/users/:id // 删除用户
// 关联资源
GET /api/users/:id/orders // 用户订单
GET /api/users/:id/orders/:oid // 用户的某个订单
// ❌ 不好的设计
GET /api/getUsers // 不应使用动词
POST /api/updateUser // 用 HTTP 方法表示动作
GET /api/users?id=123 // 路径参数优于查询参数响应格式
javascript
// ✅ 统一响应结构
{
"status": "success",
"data": {
"id": 1,
"name": "Alice",
"email": "alice@example.com",
"createdAt": "2026-01-15T08:00:00Z"
}
}
// ✅ 分页
{
"status": "success",
"data": [...],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 100,
"totalPages": 5
}
}
// ✅ 错误响应
{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "邮箱格式不正确",
"details": [
{ "field": "email", "message": "必须是一个有效的邮箱地址" }
]
}
}HTTP 状态码
text
200 OK - GET 成功
201 Created - POST 创建成功
204 No Content - DELETE 成功
400 Bad Request - 参数错误
401 Unauthorized - 未认证
403 Forbidden - 无权限
404 Not Found - 资源不存在
409 Conflict - 资源冲突
422 Unprocessable - 验证失败
429 Too Many Requests - 限流
500 Internal Server Error - 服务器错误GraphQL 设计
Schema 定义
graphql
# typeDefs
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
}
type Query {
user(id: ID!): User
users(page: Int, limit: Int): [User!]!
postsByUser(userId: ID!): [Post!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}查询示例
graphql
# ✅ 按需获取(GraphQL 的优势)
query {
user(id: "1") {
name
email
posts {
title
comments {
content
}
}
}
}
# ❌ 不要在一次查询中嵌套过深
query {
users {
posts {
comments {
user {
posts { # 过深嵌套会导致性能问题
title
}
}
}
}
}
}最佳实践
javascript
// N+1 问题:使用 DataLoader
const DataLoader = require('dataloader');
const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findAll({ where: { id: ids } });
return ids.map(id => users.find(u => u.id === id));
});
// 在 resolver 中使用
const resolvers = {
Post: {
author: (post) => userLoader.load(post.authorId),
},
};gRPC 设计
Proto 定义
protobuf
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (User);
rpc ListUsers (ListUsersRequest) returns (ListUsersResponse);
rpc CreateUser (CreateUserRequest) returns (User);
rpc UpdateUser (UpdateUserRequest) returns (User);
rpc DeleteUser (DeleteUserRequest) returns (Empty);
rpc SubscribeUsers (Empty) returns (stream User); // 服务端流
}
message User {
int32 id = 1;
string name = 2;
string email = 3;
string created_at = 4;
}
message GetUserRequest {
int32 id = 1;
}
message ListUsersRequest {
int32 page = 1;
int32 page_size = 2;
}流式通信
protobuf
// 服务端流式(数据推送)
service StockService {
rpc SubscribePrice (StockRequest) returns (stream StockPrice);
}
// 客户端流式(批量上传)
service FileService {
rpc UploadFile (stream FileChunk) returns (UploadResponse);
}
// 双向流式(实时通信)
service ChatService {
rpc Chat (stream ChatMessage) returns (stream ChatMessage);
}通用最佳实践
1. 版本管理
javascript
// URL 路径版本(REST)
/api/v1/users
/api/v2/users
// Header 版本
Accept: application/vnd.api+json;version=2
// GraphQL Schema 共存
const server = new ApolloServer({
schema: [v1Schema, v2Schema], // 逐步迁移
});2. 认证与授权
javascript
// 推荐:Bearer Token
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// 中间件示例
function authMiddleware(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: '未认证' });
try {
req.user = jwt.verify(token, SECRET);
next();
} catch {
res.status(401).json({ error: 'Token 无效' });
}
}3. 请求验证
javascript
// Zod 验证示例
import { z } from 'zod';
const createUserSchema = z.object({
name: z.string().min(2).max(50),
email: z.string().email(),
age: z.number().min(0).max(150).optional(),
});
app.post('/api/users', (req, res) => {
const result = createUserSchema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
error: 'VALIDATION_ERROR',
details: result.error.issues,
});
}
// result.data 是安全的
});4. 速率限制
javascript
// 基于 Token Bucket 的实现
const rateLimit = new Map();
function rateLimiter(ip, limit = 100, window = 60000) {
const now = Date.now();
const record = rateLimit.get(ip) || { count: 0, reset: now + window };
if (now > record.reset) {
record.count = 0;
record.reset = now + window;
}
record.count++;
rateLimit.set(ip, record);
if (record.count > limit) {
throw { status: 429, message: '请求过于频繁' };
}
}5. 文档
javascript
// OpenAPI 3.0 规范(REST)
/**
* @openapi
* /api/users:
* get:
* summary: 获取用户列表
* parameters:
* - in: query
* name: page
* schema:
* type: integer
* responses:
* 200:
* description: 用户列表
*/选型决策树
text
项目是否需要实时/流式通信?
├── 是 → gRPC
└── 否 → 客户端是否需要灵活的数据查询?
├── 是 → GraphQL
└── 否 → REST总结
设计 API 没有银弹。REST 简单直接适合大多数人,GraphQL 灵活强大适合数据复杂场景,gRPC 高性能适合微服务通信。
重要的是:一致性 —— 无论选哪种风格,在整个项目中保持一致,比选「最好」的风格更重要。
推荐指数:⭐⭐⭐⭐⭐
在线工具:https://swagger.io/tools/swagger-ui/
💬 评论区 (0)
暂无评论,快来抢沙发吧!