前端工程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/comments

1.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,email

03.二、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=2

3.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 网关,请阅读后续专题。*