前言

良好的 API 设计是构建成功应用的关键。一个设计良好的 API 应该直观、一致、可预测,并且易于开发者使用和维护。本文将分享 RESTful API 设计的最佳实践。

RESTful 基础原则

1. 资源导向设计

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ❌ 不好的设计 - 动作导向
POST /getUser
POST /createUser
POST /updateUser
POST /deleteUser

// ✅ 好的设计 - 资源导向
GET    /users           // 获取用户列表
POST   /users           // 创建新用户
GET    /users/{id}      // 获取特定用户
PUT    /users/{id}      // 完整更新用户
PATCH  /users/{id}      // 部分更新用户
DELETE /users/{id}      // 删除用户

2. HTTP 方法正确使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// HTTP 方法语义
const httpMethods = {
  GET: '获取资源,幂等,安全',
  POST: '创建资源,非幂等,不安全',
  PUT: '完整更新资源,幂等,不安全',
  PATCH: '部分更新资源,幂等,不安全',
  DELETE: '删除资源,幂等,不安全'
};

// 资源嵌套
GET    /users/{userId}/orders           // 获取用户的订单列表
POST   /users/{userId}/orders           // 为用户创建新订单
GET    /users/{userId}/orders/{orderId} // 获取特定订单

URL 设计规范

1. 命名规范

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// ✅ 使用复数形式表示资源集合
GET /users
GET /products
GET /orders

// ✅ 使用小写字母和连字符
GET /user-profiles/{id}
GET /order-items/{id}

// ✅ 避免深层嵌套(最多2-3层)
GET /users/{userId}/orders/{orderId}/items
// 或使用查询参数
GET /items?userId={userId}&orderId={orderId}

// ❌ 避免的做法
GET /User
GET /getUsers
GET /api/v1/getAllUsers
GET /users/{userId}/order/{orderId}/item/{itemId}/details/{detailId}

2. 版本控制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// URL 版本控制
GET /api/v1/users
GET /api/v2/users

// Header 版本控制
Accept: application/vnd.api+json;version=1
Accept: application/vnd.myapi.v2+json

// 自定义 Header 版本控制
API-Version: v1

// 版本升级策略
// v1 - 稳定版本,向后兼容
// v2 - 新版本,可能不兼容
// v1.1 - 小版本更新,向后兼容

请求和响应设计

1. 请求体结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 创建资源请求
POST /api/v1/users
Content-Type: application/json

{
  "user": {
    "name": "John Doe",
    "email": "john@example.com",
    "age": 30,
    "preferences": {
      "language": "en",
      "timezone": "UTC"
    }
  }
}

// 批量操作请求
POST /api/v1/users/batch
Content-Type: application/json

{
  "users": [
    {
      "name": "User 1",
      "email": "user1@example.com"
    },
    {
      "name": "User 2",
      "email": "user2@example.com"
    }
  ]
}

2. 响应体结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 成功响应
{
  "success": true,
  "data": {
    "id": "123",
    "name": "John Doe",
    "email": "john@example.com",
    "createdAt": "2025-12-06T15:30:00Z",
    "updatedAt": "2025-12-06T15:30:00Z"
  },
  "meta": {
    "timestamp": "2025-12-06T15:30:00Z",
    "version": "v1"
  }
}

// 列表响应
{
  "success": true,
  "data": [
    {
      "id": "123",
      "name": "John Doe"
    },
    {
      "id": "124",
      "name": "Jane Smith"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "totalPages": 5,
    "hasNext": true,
    "hasPrev": false
  }
}

// 错误响应
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input data",
    "details": [
      {
        "field": "email",
        "message": "Email is required"
      },
      {
        "field": "age",
        "message": "Age must be between 0 and 120"
      }
    ]
  },
  "meta": {
    "timestamp": "2025-12-06T15:30:00Z",
    "requestId": "req-123456"
  }
}

状态码使用指南

1. 成功状态码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 200 OK - 成功获取或更新资源
GET /api/v1/users/123 -> 200
PUT /api/v1/users/123 -> 200

// 201 Created - 成功创建资源
POST /api/v1/users -> 201

// 202 Accepted - 请求已接受,正在处理
POST /api/v1/users/export -> 202

// 204 No Content - 成功删除资源
DELETE /api/v1/users/123 -> 204

// 206 Partial Content - 部分内容(分页)
GET /api/v1/users?page=1&limit=10 -> 206

2. 错误状态码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 400 Bad Request - 客户端错误
POST /api/v1/users -> 400 (验证失败)

// 401 Unauthorized - 未认证
GET /api/v1/users -> 401 (需要登录)

// 403 Forbidden - 无权限
GET /api/v1/admin/users -> 403 (需要管理员权限)

// 404 Not Found - 资源不存在
GET /api/v1/users/999 -> 404

// 409 Conflict - 资源冲突
POST /api/v1/users -> 409 (邮箱已存在)

// 422 Unprocessable Entity - 请求格式正确但语义错误
POST /api/v1/users -> 422 (业务逻辑错误)

// 429 Too Many Requests - 请求频率限制
GET /api/v1/users -> 429 (超出限制)

数据验证和过滤

1. 输入验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 使用 Joi 进行验证
const Joi = require('joi');

const userSchema = Joi.object({
  name: Joi.string().min(2).max(50).required(),
  email: Joi.string().email().required(),
  age: Joi.number().integer().min(0).max(120),
  preferences: Joi.object({
    language: Joi.string().valid('en', 'zh', 'es'),
    timezone: Joi.string()
  })
});

// 验证中间件
const validateRequest = (schema) => {
  return (req, res, next) => {
    const { error } = schema.validate(req.body);

    if (error) {
      return res.status(400).json({
        success: false,
        error: {
          code: 'VALIDATION_ERROR',
          message: 'Invalid input data',
          details: error.details.map(d => ({
            field: d.path.join('.'),
            message: d.message
          }))
        }
      });
    }

    next();
  };
};

// 使用
app.post('/api/v1/users', validateRequest(userSchema), (req, res) => {
  // 处理逻辑
});

2. 响应过滤

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 字段选择
GET /api/v1/users?fields=id,name,email

// 实现示例
app.get('/api/v1/users', (req, res) => {
  const { fields } = req.query;

  let users = await User.find();

  if (fields) {
    const selectedFields = fields.split(',');
    users = users.map(user => _.pick(user, selectedFields));
  }

  res.json({ success: true, data: users });
});

// 数据转换
const userSerializer = (user) => {
  return {
    id: user._id,
    name: user.name,
    email: user.email,
    avatar: user.profile.avatar,
    joinedAt: user.createdAt.toISOString(),
    // 不返回敏感信息
    // password: user.password
  };
};

// 嵌入关联数据
GET /api/v1/users?include=profile,orders

app.get('/api/v1/users', async (req, res) => {
  const { include } = req.query;
  const includes = include ? include.split(',') : [];

  let users = await User.find();

  if (includes.includes('profile')) {
    users = await User.populate(users, 'profile');
  }

  if (includes.includes('orders')) {
    users = await User.populate(users, 'orders');
  }

  res.json({ success: true, data: users });
});

分页、排序和过滤

1. 分页实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 查询参数
GET /api/v1/users?page=1&limit=20

// 响应结构
{
  "success": true,
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 100,
    "totalPages": 5,
    "hasNext": true,
    "hasPrev": false,
    "next": "/api/v1/users?page=2&limit=20",
    "prev": null
  }
}

// 实现示例
app.get('/api/v1/users', async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 20;
  const skip = (page - 1) * limit;

  const [users, total] = await Promise.all([
    User.find().skip(skip).limit(limit),
    User.countDocuments()
  ]);

  const totalPages = Math.ceil(total / limit);

  res.json({
    success: true,
    data: users,
    pagination: {
      page,
      limit,
      total,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
      next: page < totalPages ?
        `/api/v1/users?page=${page + 1}&limit=${limit}` : null,
      prev: page > 1 ?
        `/api/v1/users?page=${page - 1}&limit=${limit}` : null
    }
  });
});

2. 排序实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 查询参数
GET /api/v1/users?sort=createdAt&order=desc
GET /api/v1/users?sort=name,age&order=asc,desc

// 实现示例
app.get('/api/v1/users', async (req, res) => {
  const { sort = 'createdAt', order = 'desc' } = req.query;

  const sortFields = sort.split(',');
  const sortOrders = order.split(',');

  const sortObj = {};
  sortFields.forEach((field, index) => {
    sortObj[field] = sortOrders[index] === 'desc' ? -1 : 1;
  });

  const users = await User.find().sort(sortObj);

  res.json({ success: true, data: users });
});

3. 过滤实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 查询参数
GET /api/v1/users?age=30
GET /api/v1/users?age_min=18&age_max=65
GET /api/v1/users?name=john&email=@example.com
GET /api/v1/users?tags=admin,active

// 实现示例
app.get('/api/v1/users', async (req, res) => {
  const filters = {};

  // 精确匹配
  if (req.query.age) {
    filters.age = parseInt(req.query.age);
  }

  // 范围查询
  if (req.query.age_min || req.query.age_max) {
    filters.age = {};
    if (req.query.age_min) {
      filters.age.$gte = parseInt(req.query.age_min);
    }
    if (req.query.age_max) {
      filters.age.$lte = parseInt(req.query.age_max);
    }
  }

  // 模糊查询
  if (req.query.name) {
    filters.name = { $regex: req.query.name, $options: 'i' };
  }

  // 多值查询
  if (req.query.tags) {
    const tags = req.query.tags.split(',');
    filters.tags = { $in: tags };
  }

  const users = await User.find(filters);

  res.json({ success: true, data: users });
});

认证和授权

1. JWT 认证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 生成 token
const generateToken = (user) => {
  return jwt.sign(
    {
      userId: user.id,
      email: user.email,
      role: user.role
    },
    process.env.JWT_SECRET,
    { expiresIn: '24h' }
  );
};

// 认证中间件
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({
      success: false,
      error: {
        code: 'TOKEN_MISSING',
        message: 'Access token is required'
      }
    });
  }

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(401).json({
        success: false,
        error: {
          code: 'TOKEN_INVALID',
          message: 'Invalid or expired token'
        }
      });
    }

    req.user = user;
    next();
  });
};

2. 授权中间件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 角色授权
const authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        success: false,
        error: {
          code: 'INSUFFICIENT_PERMISSIONS',
          message: 'You do not have permission to perform this action'
        }
      });
    }
    next();
  };
};

// 资源所有权检查
const checkOwnership = (resourceModel) => {
  return async (req, res, next) => {
    try {
      const resource = await resourceModel.findById(req.params.id);

      if (!resource) {
        return res.status(404).json({
          success: false,
          error: {
            code: 'RESOURCE_NOT_FOUND',
            message: 'Resource not found'
          }
        });
      }

      if (resource.userId.toString() !== req.user.userId && req.user.role !== 'admin') {
        return res.status(403).json({
          success: false,
          error: {
            code: 'ACCESS_DENIED',
            message: 'You can only access your own resources'
          }
        });
      }

      req.resource = resource;
      next();
    } catch (error) {
      next(error);
    }
  };
};

// 使用示例
app.delete(
  '/api/v1/users/:id',
  authenticateToken,
  checkOwnership(User),
  (req, res) => {
    // 删除逻辑
  }
);

缓存策略

1. HTTP 缓存头

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 设置缓存头
const setCacheHeaders = (req, res, next) => {
  // 公共缓存,1小时
  if (req.path.startsWith('/api/v1/public/')) {
    res.set('Cache-Control', 'public, max-age=3600');
  }

  // 私有缓存,5分钟
  else if (req.path.startsWith('/api/v1/users/profile')) {
    res.set('Cache-Control', 'private, max-age=300');
  }

  // 不缓存
  else {
    res.set('Cache-Control', 'no-cache, no-store, must-revalidate');
  }

  // ETag
  if (req.method === 'GET') {
    const data = JSON.stringify(res.locals.data);
    const etag = require('crypto').createHash('md5').update(data).digest('hex');
    res.set('ETag', etag);

    if (req.headers['if-none-match'] === etag) {
      return res.status(304).end();
    }
  }

  next();
};

2. Redis 缓存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const redis = require('redis');
const client = redis.createClient();

const cache = {
  get: async (key) => {
    try {
      const data = await client.get(key);
      return data ? JSON.parse(data) : null;
    } catch (error) {
      return null;
    }
  },

  set: async (key, data, ttl = 3600) => {
    try {
      await client.setex(key, ttl, JSON.stringify(data));
    } catch (error) {
      console.error('Cache set error:', error);
    }
  },

  del: async (key) => {
    try {
      await client.del(key);
    } catch (error) {
      console.error('Cache delete error:', error);
    }
  }
};

// 缓存中间件
const cacheMiddleware = (ttl = 3600) => {
  return async (req, res, next) => {
    const key = `cache:${req.originalUrl}`;

    // 尝试从缓存获取
    const cachedData = await cache.get(key);

    if (cachedData) {
      return res.json({
        ...cachedData,
        cached: true
      });
    }

    // 重写 res.json 以缓存响应
    const originalJson = res.json;
    res.json = function(data) {
      cache.set(key, data, ttl);
      return originalJson.call(this, data);
    };

    next();
  };
};

// 使用示例
app.get('/api/v1/users', cacheMiddleware(300), async (req, res) => {
  const users = await User.find();
  res.json({ success: true, data: users });
});

API 文档

1. Swagger/OpenAPI 配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# swagger.yaml
openapi: 3.0.0
info:
  title: User API
  version: 1.0.0
  description: 用户管理 API

paths:
  /api/v1/users:
    get:
      summary: 获取用户列表
      tags:
        - Users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
      responses:
        '200':
          description: 成功返回用户列表
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/User'
                  pagination:
                    $ref: '#/components/schemas/Pagination'

    post:
      summary: 创建新用户
      tags:
        - Users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: 用户创建成功
        '400':
          description: 请求参数错误
        '409':
          description: 用户已存在

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        email:
          type: string
          format: email
        createdAt:
          type: string
          format: date-time

    CreateUserRequest:
      type: object
      required:
        - name
        - email
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 50
        email:
          type: string
          format: email
        age:
          type: integer
          minimum: 0
          maximum: 120

总结

RESTful API 设计的最佳实践:

  1. 资源导向:使用名词而非动词,遵循 REST 原则
  2. URL 设计:清晰、一致、可预测的 URL 结构
  3. HTTP 方法:正确使用 HTTP 动词和状态码
  4. 数据格式:统一的请求和响应格式
  5. 验证过滤:严格的输入验证和灵活的数据过滤
  6. 认证授权:安全的身份验证和细粒度的权限控制
  7. 性能优化:合理的缓存策略和分页机制
  8. 文档完善:清晰、详细的 API 文档

遵循这些实践,可以构建出易于理解、使用和维护的 API 接口。


相关工具: