前端工程Technical Deep Dive
RESTful API 设计最佳实践
发布时间2026/03/29
分类前端工程
预计阅读12 分钟
作者吴长龙
*
RESTful API 设计最佳实践
01.内容
# RESTful API 设计最佳实践
一个好的 API 不仅要功能正确,还要易于理解、使用和维护。本文从 URL 设计、HTTP 动词、状态码、版本控制、安全性等多个维度,系统介绍企业级 RESTful API 的设计原则和最佳实践。
02.一、URL 设计原则
1.1 资源导向
URL 应该描述"资源"而不是"动作":
javascript snippetjavascript
// ❌ 错误:使用动词
GET /api/getUsers
GET /api/getUserById/123
POST /api/createUser
PUT /api/updateUser/123
DELETE /api/deleteUser/123
// ✅ 正确:使用资源名词
GET /api/users # 获取用户列表
GET /api/users/123 # 获取指定用户
POST /api/users # 创建新用户
PUT /api/users/123 # 完整更新用户
PATCH /api/users/123 # 部分更新用户
DELETE /api/users/123 # 删除用户1.2 层级结构
使用合理的层级表达资源关系:
javascript snippetjavascript
// 用户和订单的关系
GET /api/users/123/orders # 获取用户123的所有订单
GET /api/users/123/orders/456 # 获取用户123的456号订单
POST /api/users/123/orders # 为用户123创建订单
// 订单和商品的关系
GET /api/orders/456/items # 获取订单456的所有商品
// 嵌套层级不宜过深(建议不超过3层)
// ❌ /api/users/123/orders/456/items/789/comments
// ✅ /api/orders/456/items/789/comments1.3 复数形式
javascript snippetjavascript
// ✅ 使用复数形式
GET /api/users
GET /api/products
GET /api/orders
// 特殊情况:单个资源可能用单数
GET /api/users/me # 获取当前用户
GET /api/auth/status # 认证状态(非资源)1.4 查询参数
javascript snippetjavascript
// 分页
GET /api/users?page=2&limit=20
// 排序
GET /api/users?sort=createdAt&order=desc
// 过滤
GET /api/users?role=admin&status=active
// 搜索
GET /api/users?q=john&fields=name,email
// 关联展开
GET /api/users/123?include=orders,posts
// 字段选择
GET /api/users/123?fields=id,name,email03.二、HTTP 动词的正确使用
2.1 常用动词
| 动词 | 语义 | 幂等 | 用途 |
|---|---|---|---|
| GET | 获取资源 | ✅ | 查询 |
| POST | 创建资源 | ❌ | 新建 |
| PUT | 完整更新 | ✅ | 完整更新 |
| PATCH | 部分更新 | ❌ | 部分更新 |
| DELETE | 删除资源 | ✅ | 删除 |
2.2 复杂操作示例
javascript snippetjavascript
// 批量操作:使用 POST 实现
POST /api/users/batch-delete # 批量删除
POST /api/users/batch-update # 批量更新
// 请求体: { ids: [1, 2, 3], action: 'delete' }
// 复杂操作:使用 action 字段
POST /api/orders/123/cancel # 取消订单
POST /api/orders/123/pay # 支付订单
POST /api/users/123/activate # 激活用户
// 或者使用更明确的 RESTful 风格
POST /api/orders/123/actions # 订单操作
// { "action": "cancel", "reason": "用户取消" }2.3 响应状态码
javascript snippetjavascript
// 成功状态码
200 OK // GET/PUT 成功
201 Created // POST 创建成功
204 No Content // DELETE 成功,无返回内容
// 客户端错误状态码
400 Bad Request // 请求参数错误
401 Unauthorized // 未认证
403 Forbidden // 无权限
404 Not Found // 资源不存在
409 Conflict // 业务冲突(如重复提交)
422 Unprocessable Entity // 验证失败
429 Too Many Requests // 请求过于频繁
// 服务端错误状态码
500 Internal Server Error // 服务器内部错误
503 Service Unavailable // 服务不可用2.4 完整响应示例
javascript snippetjavascript
// GET /api/users/123 - 成功
{
"data": {
"id": "123",
"name": "张三",
"email": "zhangsan@example.com",
"createdAt": "2026-01-15T08:00:00Z"
}
}
// GET /api/users - 成功(分页)
{
"data": [
{ "id": "123", "name": "张三" },
{ "id": "124", "name": "李四" }
],
"pagination": {
"page": 1,
"limit": 20,
"total": 100,
"totalPages": 5
}
}
// POST /api/users - 201 Created
{
"data": {
"id": "125",
"name": "王五",
"email": "wangwu@example.com",
"createdAt": "2026-03-29T12:00:00Z"
},
"message": "用户创建成功"
}
// 400 Bad Request
{
"error": {
"code": "VALIDATION_ERROR",
"message": "请求参数验证失败",
"details": [
{ "field": "email", "message": "邮箱格式不正确" },
{ "field": "password", "message": "密码长度至少8位" }
]
}
}
// 401 Unauthorized
{
"error": {
"code": "UNAUTHORIZED",
"message": "请先登录"
}
}04.三、版本控制
3.1 版本控制策略
javascript snippetjavascript
// 方案一:URL 路径(推荐)
GET /api/v1/users
GET /api/v2/users
// 方案二:请求头
GET /api/users
Accept: application/vnd.myapp.v2+json
// 方案三:查询参数(不推荐)
GET /api/users?version=23.2 版本演进策略
javascript snippetjavascript
// v1 版本
{
"id": "123",
"name": "张三",
"email": "zhangsan@example.com"
}
// v2 版本:新增字段,废弃字段
{
"id": "123",
"name": "张三",
"email": "zhangsan@example.com",
"avatar": "https://...", // 新增
"nickname": "小张", // 新增
"isActive": true // 新增
}
// 废弃字段处理
// 在响应中标记 deprecated
{
"data": { ... },
"deprecated": ["email"],
"deprecationWarning": "email 字段将在 v3 中移除,请使用 contact.email"
}3.3 版本兼容性
javascript snippetjavascript
// 后端兼容多版本的实现
app.get('/api/users', async (req, res) => {
const version = req.apiVersion || 'v1';
const users = await getUsers();
if (version === 'v1') {
return users.map(u => ({
id: u.id,
name: u.name,
email: u.email
}));
}
if (version === 'v2') {
return users.map(u => ({
id: u.id,
name: u.name,
email: u.email,
avatar: u.avatar,
nickname: u.nickname,
isActive: u.isActive
}));
}
});05.四、认证与授权
4.1 认证方式
javascript snippetjavascript
// 方式一:Bearer Token(JWT)
// 请求头: Authorization: Bearer <token>
const authMiddleware = async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: '缺少认证令牌' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: '令牌无效或已过期' });
}
};
// 方式二:API Key(适合服务端到服务端)
// 请求头: X-API-Key: <key>
const apiKeyMiddleware = async (req, res, next) => {
const apiKey = req.headers['x-api-key'];
const app = await validateApiKey(apiKey);
if (!app) {
return res.status(401).json({ error: '无效的 API Key' });
}
req.app = app;
next();
};
// 方式三:OAuth 2.0(第三方登录)
// 用于社交登录、企业SSO等场景4.2 授权控制
javascript snippetjavascript
// 基于角色的访问控制(RBAC)
const authorize = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: '请先登录' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: '权限不足' });
}
next();
};
};
// 使用
app.get('/api/admin/users',
authMiddleware,
authorize('admin'),
listAllUsers
);
app.get('/api/users',
authMiddleware,
authorize('admin', 'user'),
listUsers
);
// 资源级别的授权
app.delete('/api/users/:id', authMiddleware, async (req, res) => {
// 只能删除自己,或者管理员可以删除任何人
if (req.user.role !== 'admin' && req.user.id !== req.params.id) {
return res.status(403).json({ error: '只能删除自己的账户' });
}
await deleteUser(req.params.id);
res.status(204).send();
});4.3 限流保护
javascript snippetjavascript
// 使用 Redis 实现分布式限流
const rateLimit = require('express-rate-limit');
const RedisStore = require('rate-limit-redis');
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
// API 限流
const apiLimiter = rateLimit({
store: new RedisStore({
// @ts-expect-error - known issue with TypeScript
sendCommand: (...args) => redis.call(...args),
}),
windowMs: 15 * 60 * 1000, // 15分钟窗口
max: 100, // 最多100次请求
message: {
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: '请求过于频繁,请稍后再试'
}
},
standardHeaders: true,
legacyHeaders: false,
});
// 登录限流
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // 15分钟内最多尝试5次
message: {
error: {
code: 'LOGIN_LIMIT_EXCEEDED',
message: '登录尝试次数过多,请15分钟后再试'
}
}
});
app.use('/api/', apiLimiter);
app.post('/api/auth/login', loginLimiter, loginHandler);06.五、错误处理规范
5.1 统一错误格式
javascript snippetjavascript
// 错误响应结构
{
"error": {
"code": "ERROR_CODE", // 错误码(用于程序识别)
"message": "人类可读的错误描述", // 对用户可见
"details": [...], // 详细错误信息(可选)
"requestId": "req_abc123" // 请求追踪ID(可选)
}
}
// 常见错误码
const ErrorCodes = {
// 认证授权
UNAUTHORIZED: 'UNAUTHORIZED',
FORBIDDEN: 'FORBIDDEN',
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
// 资源
NOT_FOUND: 'NOT_FOUND',
RESOURCE_CONFLICT: 'RESOURCE_CONFLICT',
// 请求
VALIDATION_ERROR: 'VALIDATION_ERROR',
BAD_REQUEST: 'BAD_REQUEST',
// 服务器
INTERNAL_ERROR: 'INTERNAL_ERROR',
SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE'
};5.2 全局错误处理
javascript snippetjavascript
// Express
app.use((err, req, res, next) => {
console.error('错误:', err);
// 已知错误
if (err.isKnown) {
return res.status(err.statusCode).json({
error: {
code: err.code,
message: err.message,
details: err.details
}
});
}
// 未知错误
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: '服务器内部错误',
requestId: req.id
}
});
});
// Fastify
app.setErrorHandler((error, request, reply) => {
request.log.error(error);
if (error.statusCode === 400) {
return reply.status(400).send({
error: { code: 'BAD_REQUEST', message: error.message }
});
}
return reply.status(500).send({
error: { code: 'INTERNAL_ERROR', message: '服务器内部错误' }
});
});07.六、API 文档
6.1 OpenAPI/Swagger
yaml snippetyaml
# openapi.yaml
openapi: 3.0.3
info:
title: 用户管理 API
version: 1.0.0
description: 提供用户完整的 CRUD 功能
servers:
- url: https://api.example.com/v1
description: 生产环境
- url: https://staging-api.example.com/v1
description: 测试环境
paths:
/users:
get:
summary: 获取用户列表
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
responses:
'200':
description: 成功
content:
application/json:
schema:
$ref: '#/components/schemas/UsersResponse'
'401':
$ref: '#/components/responses/Unauthorized'
components:
schemas:
User:
type: object
properties:
id:
type: string
name:
type: string
email:
type: string
format: email
UsersResponse:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
responses:
Unauthorized:
description: 未授权
content:
application/json:
schema:
$ref: '#/components/schemas/Error'6.2 自动生成文档
javascript snippetjavascript
// Fastify + swagger
const fastify = require('fastify');
const swagger = require('@fastify/swagger');
const swaggerUi = require('@fastify/swagger-ui');
const app = fastify();
await app.register(swagger, {
openapi: {
info: {
title: '用户管理 API',
version: '1.0.0'
}
}
});
await app.register(swaggerUi, {
routePrefix: '/docs'
});
// 访问 http://localhost:3000/docs 查看交互式文档08.七、最佳实践清单
- •[ ] URL 使用资源名词,复数形式
- •[ ] 正确使用 HTTP 动词(GET/POST/PUT/PATCH/DELETE)
- •[ ] 返回合适的状态码
- •[ ] 统一错误响应格式
- •[ ] 实现 API 版本控制
- •[ ] 认证和授权机制
- •[ ] 实现限流保护
- •[ ] 提供 OpenAPI 文档
- •[ ] 支持分页和过滤
- •[ ] 实现请求日志
---
*遵循这些原则设计的 API,不仅易于前端调用,也便于后续维护和扩展。如果你想了解如何构建高性能的 AI API 网关,请阅读后续专题。*