Skip to content

RESTful API设计

概述

REST(Representational State Transfer,表述性状态转移)是一种软件架构风格,用于设计网络应用程序的API。RESTful API是基于REST原则设计的Web API。

核心原则

REST的六大约束

  1. 客户端-服务器分离: 客户端和服务器独立演化
  2. 无状态: 每个请求包含所有必要信息
  3. 可缓存: 响应必须明确是否可以缓存
  4. 统一接口: 使用标准HTTP方法
  5. 分层系统: 支持代理、网关等中间层
  6. 按需代码: 服务器可以临时扩展客户端功能

REST vs 传统API

特性传统APIRESTful API
设计风格基于动作基于资源
URL设计/getUser?id=123/users/123
HTTP方法主要使用GET/POST充分利用所有HTTP方法
状态管理可能依赖会话无状态
数据格式通常是XML通常是JSON

资源设计

资源命名规范

javascript
// ✅ 好的设计 - 使用名词复数
GET /users              // 获取用户列表
GET /users/123          // 获取特定用户
POST /users             // 创建用户
PUT /users/123          // 更新用户
DELETE /users/123       // 删除用户

// ❌ 不好的设计 - 使用动词
GET /getUsers
POST /createUser
PUT /updateUser
DELETE /deleteUser

资源层级关系

javascript
// 用户和订单的关系
GET /users/123/orders           // 获取用户的订单列表
GET /users/123/orders/456       // 获取用户的特定订单
POST /users/123/orders          // 为用户创建订单

// 避免过深的嵌套
// ❌ 不推荐
GET /users/123/orders/456/items/789/comments/101

// ✅ 推荐 - 扁平化设计
GET /orders/456/items
GET /comments?itemId=789

集合和单个资源

javascript
// 集合资源
GET /users                    // 用户列表
POST /users                   // 创建用户

// 单个资源
GET /users/123                // 特定用户
PUT /users/123                // 更新用户
DELETE /users/123             // 删除用户

// 子资源
GET /users/123/profile        // 用户资料
PUT /users/123/profile        // 更新用户资料

HTTP方法使用

标准HTTP方法

javascript
// GET - 获取资源
GET /users                    // 获取用户列表
GET /users/123                // 获取特定用户
GET /users?page=1&limit=10    // 分页查询

// POST - 创建资源
POST /users                   // 创建新用户
POST /users/123/orders        // 为用户创建订单

// PUT - 完整更新资源
PUT /users/123                // 完整更新用户信息
PUT /users/123/profile        // 完整更新用户资料

// PATCH - 部分更新资源
PATCH /users/123              // 部分更新用户信息
PATCH /users/123/profile      // 部分更新用户资料

// DELETE - 删除资源
DELETE /users/123             // 删除用户
DELETE /users/123/orders/456  // 删除用户的特定订单

自定义操作

javascript
// 对于不适合标准HTTP方法的操作,使用POST
POST /users/123/activate      // 激活用户
POST /users/123/deactivate    // 停用用户
POST /orders/456/cancel       // 取消订单
POST /orders/456/refund       // 退款

// 或者使用查询参数
PUT /users/123?action=activate

URL设计最佳实践

使用复数名词

javascript
// ✅ 推荐
GET /users
GET /products
GET /orders

// ❌ 避免
GET /user
GET /product
GET /order

使用小写字母和连字符

javascript
// ✅ 推荐
GET /user-profiles
GET /order-items
GET /product-categories

// ❌ 避免
GET /userProfiles
GET /order_items
GET /ProductCategories

版本控制

javascript
// 在URL中包含版本
GET /api/v1/users
GET /api/v2/users

// 或在头部中包含版本
GET /api/users
Headers: Accept: application/vnd.company.v1+json

响应设计

标准响应格式

javascript
// 成功响应
{
  "success": true,
  "data": {
    "id": 123,
    "name": "张三",
    "email": "[email protected]"
  },
  "message": "用户创建成功"
}

// 列表响应
{
  "success": true,
  "data": [
    {
      "id": 123,
      "name": "张三",
      "email": "[email protected]"
    },
    {
      "id": 124,
      "name": "李四",
      "email": "[email protected]"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 100,
    "totalPages": 10
  }
}

// 错误响应
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "数据验证失败",
    "details": [
      {
        "field": "email",
        "message": "邮箱格式不正确"
      }
    ]
  }
}

HTTP状态码使用

javascript
// 2xx 成功
200 OK                    // 请求成功
201 Created               // 资源创建成功
204 No Content            // 请求成功,无返回内容

// 4xx 客户端错误
400 Bad Request           // 请求参数错误
401 Unauthorized          // 未授权
403 Forbidden             // 禁止访问
404 Not Found             // 资源不存在
409 Conflict              // 资源冲突
422 Unprocessable Entity  // 无法处理的实体

// 5xx 服务器错误
500 Internal Server Error // 服务器内部错误
503 Service Unavailable   // 服务不可用

查询参数设计

分页参数

javascript
// 标准分页参数
GET /users?page=1&limit=10&offset=0

// 基于游标的分页
GET /users?cursor=eyJpZCI6MTIzfQ&limit=10

// 响应包含分页信息
{
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 100,
    "totalPages": 10,
    "hasNext": true,
    "hasPrev": false
  }
}

过滤参数

javascript
// 简单过滤
GET /users?status=active
GET /users?role=admin

// 范围过滤
GET /users?age_min=18&age_max=30
GET /users?created_at_gte=2024-01-01

// 数组过滤
GET /users?roles[]=admin&roles[]=user

// 搜索
GET /users?search=张三
GET /users?q=zhangsan

排序参数

javascript
// 单字段排序
GET /users?sort=name
GET /users?sort=-created_at  // 降序

// 多字段排序
GET /users?sort=name,created_at
GET /users?sort=name,-created_at

// 使用order参数
GET /users?order_by=name&order=desc

实际应用示例

用户管理API

javascript
const express = require('express');
const router = express.Router();

// 获取用户列表
router.get('/users', async (req, res) => {
  try {
    const { page = 1, limit = 10, search, status, role } = req.query;
    
    // 构建查询条件
    const where = {};
    if (search) where.name = { $regex: search, $options: 'i' };
    if (status) where.status = status;
    if (role) where.role = role;
    
    // 执行查询
    const users = await User.find(where)
      .skip((page - 1) * limit)
      .limit(parseInt(limit))
      .sort({ createdAt: -1 });
    
    const total = await User.countDocuments(where);
    
    res.json({
      success: true,
      data: users,
      pagination: {
        page: parseInt(page),
        limit: parseInt(limit),
        total,
        totalPages: Math.ceil(total / limit)
      }
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: { message: '获取用户列表失败' }
    });
  }
});

// 获取单个用户
router.get('/users/:id', async (req, res) => {
  try {
    const user = await User.findById(req.params.id);
    if (!user) {
      return res.status(404).json({
        success: false,
        error: { message: '用户不存在' }
      });
    }
    
    res.json({
      success: true,
      data: user
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: { message: '获取用户信息失败' }
    });
  }
});

// 创建用户
router.post('/users', async (req, res) => {
  try {
    const user = new User(req.body);
    await user.save();
    
    res.status(201).json({
      success: true,
      data: user,
      message: '用户创建成功'
    });
  } catch (error) {
    res.status(400).json({
      success: false,
      error: { 
        message: '用户创建失败',
        details: error.message
      }
    });
  }
});

// 更新用户
router.put('/users/:id', async (req, res) => {
  try {
    const user = await User.findByIdAndUpdate(
      req.params.id,
      req.body,
      { new: true, runValidators: true }
    );
    
    if (!user) {
      return res.status(404).json({
        success: false,
        error: { message: '用户不存在' }
      });
    }
    
    res.json({
      success: true,
      data: user,
      message: '用户更新成功'
    });
  } catch (error) {
    res.status(400).json({
      success: false,
      error: { 
        message: '用户更新失败',
        details: error.message
      }
    });
  }
});

// 删除用户
router.delete('/users/:id', async (req, res) => {
  try {
    const user = await User.findByIdAndDelete(req.params.id);
    
    if (!user) {
      return res.status(404).json({
        success: false,
        error: { message: '用户不存在' }
      });
    }
    
    res.status(204).send();
  } catch (error) {
    res.status(500).json({
      success: false,
      error: { message: '用户删除失败' }
    });
  }
});

订单管理API

javascript
// 获取用户的订单
router.get('/users/:userId/orders', async (req, res) => {
  try {
    const { page = 1, limit = 10, status } = req.query;
    
    const where = { userId: req.params.userId };
    if (status) where.status = status;
    
    const orders = await Order.find(where)
      .skip((page - 1) * limit)
      .limit(parseInt(limit))
      .sort({ createdAt: -1 });
    
    const total = await Order.countDocuments(where);
    
    res.json({
      success: true,
      data: orders,
      pagination: {
        page: parseInt(page),
        limit: parseInt(limit),
        total,
        totalPages: Math.ceil(total / limit)
      }
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: { message: '获取订单列表失败' }
    });
  }
});

// 取消订单
router.post('/orders/:id/cancel', async (req, res) => {
  try {
    const order = await Order.findById(req.params.id);
    
    if (!order) {
      return res.status(404).json({
        success: false,
        error: { message: '订单不存在' }
      });
    }
    
    if (order.status !== 'pending') {
      return res.status(400).json({
        success: false,
        error: { message: '订单状态不允许取消' }
      });
    }
    
    order.status = 'cancelled';
    order.cancelledAt = new Date();
    await order.save();
    
    res.json({
      success: true,
      data: order,
      message: '订单取消成功'
    });
  } catch (error) {
    res.status(500).json({
      success: false,
      error: { message: '订单取消失败' }
    });
  }
});

错误处理

统一错误处理

javascript
// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  
  // 根据错误类型返回不同响应
  if (err.name === 'ValidationError') {
    return res.status(422).json({
      success: false,
      error: {
        code: 'VALIDATION_ERROR',
        message: '数据验证失败',
        details: Object.values(err.errors).map(e => ({
          field: e.path,
          message: e.message
        }))
      }
    });
  }
  
  if (err.name === 'CastError') {
    return res.status(400).json({
      success: false,
      error: {
        code: 'INVALID_ID',
        message: '无效的ID格式'
      }
    });
  }
  
  if (err.code === 11000) {
    return res.status(409).json({
      success: false,
      error: {
        code: 'DUPLICATE_KEY',
        message: '数据已存在'
      }
    });
  }
  
  // 默认错误
  res.status(500).json({
    success: false,
    error: {
      code: 'INTERNAL_ERROR',
      message: '服务器内部错误'
    }
  });
});

安全考虑

认证和授权

javascript
// JWT认证中间件
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: { message: '访问令牌缺失' }
    });
  }
  
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({
        success: false,
        error: { message: '访问令牌无效' }
      });
    }
    req.user = user;
    next();
  });
};

// 角色授权中间件
const authorizeRole = (roles) => {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({
        success: false,
        error: { message: '未授权访问' }
      });
    }
    
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        success: false,
        error: { message: '权限不足' }
      });
    }
    
    next();
  };
};

// 使用中间件
router.get('/admin/users', authenticateToken, authorizeRole(['admin']), (req, res) => {
  // 管理员专用接口
});

输入验证

javascript
const { body, validationResult } = require('express-validator');

// 用户创建验证
const validateCreateUser = [
  body('name').notEmpty().withMessage('姓名不能为空'),
  body('email').isEmail().withMessage('邮箱格式不正确'),
  body('password').isLength({ min: 6 }).withMessage('密码至少6位'),
  (req, res, next) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(422).json({
        success: false,
        error: {
          code: 'VALIDATION_ERROR',
          message: '数据验证失败',
          details: errors.array()
        }
      });
    }
    next();
  }
];

router.post('/users', validateCreateUser, async (req, res) => {
  // 创建用户逻辑
});

性能优化

缓存策略

javascript
const redis = require('redis');
const client = redis.createClient();

// 缓存中间件
const cache = (duration = 300) => {
  return async (req, res, next) => {
    const key = `cache:${req.originalUrl}`;
    
    try {
      const cached = await client.get(key);
      if (cached) {
        return res.json(JSON.parse(cached));
      }
      
      // 重写res.json方法以缓存响应
      const originalJson = res.json;
      res.json = function(data) {
        client.setex(key, duration, JSON.stringify(data));
        originalJson.call(this, data);
      };
      
      next();
    } catch (error) {
      next();
    }
  };
};

// 使用缓存
router.get('/users', cache(300), async (req, res) => {
  // 获取用户列表
});

分页优化

javascript
// 使用游标分页提高性能
router.get('/users', async (req, res) => {
  const { cursor, limit = 10 } = req.query;
  
  let query = {};
  if (cursor) {
    query._id = { $gt: cursor };
  }
  
  const users = await User.find(query)
    .limit(parseInt(limit) + 1) // 多查询一个用于判断是否有下一页
    .sort({ _id: 1 });
  
  const hasNext = users.length > limit;
  if (hasNext) {
    users.pop(); // 移除多余的一个
  }
  
  res.json({
    success: true,
    data: users,
    pagination: {
      hasNext,
      nextCursor: hasNext ? users[users.length - 1]._id : null
    }
  });
});

文档和测试

API文档

javascript
// 使用Swagger生成API文档
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');

const options = {
  definition: {
    openapi: '3.0.0',
    info: {
      title: '用户管理API',
      version: '1.0.0',
      description: '用户管理系统的RESTful API'
    },
    servers: [
      {
        url: 'http://localhost:3000/api',
        description: '开发服务器'
      }
    ]
  },
  apis: ['./routes/*.js']
};

const specs = swaggerJsdoc(options);
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));

/**
 * @swagger
 * /users:
 *   get:
 *     summary: 获取用户列表
 *     parameters:
 *       - in: query
 *         name: page
 *         schema:
 *           type: integer
 *         description: 页码
 *       - in: query
 *         name: limit
 *         schema:
 *           type: integer
 *         description: 每页数量
 *     responses:
 *       200:
 *         description: 成功获取用户列表
 */

测试示例

javascript
const request = require('supertest');
const app = require('../app');

describe('User API', () => {
  test('GET /users should return user list', async () => {
    const response = await request(app)
      .get('/api/users')
      .expect(200);
    
    expect(response.body.success).toBe(true);
    expect(Array.isArray(response.body.data)).toBe(true);
  });
  
  test('POST /users should create new user', async () => {
    const userData = {
      name: '测试用户',
      email: '[email protected]',
      password: '123456'
    };
    
    const response = await request(app)
      .post('/api/users')
      .send(userData)
      .expect(201);
    
    expect(response.body.success).toBe(true);
    expect(response.body.data.name).toBe(userData.name);
  });
});

总结

设计优秀的RESTful API需要遵循以下原则:

  1. 资源导向: 使用名词而不是动词
  2. HTTP方法: 充分利用标准HTTP方法
  3. 无状态: 每个请求包含所有必要信息
  4. 统一接口: 保持API的一致性
  5. 错误处理: 使用标准HTTP状态码
  6. 版本控制: 合理管理API版本
  7. 安全考虑: 实施认证、授权和验证
  8. 性能优化: 使用缓存和分页
  9. 文档完善: 提供清晰的API文档

遵循这些原则可以设计出易用、可维护、高性能的RESTful API。