RESTful API设计
概述
REST(Representational State Transfer,表述性状态转移)是一种软件架构风格,用于设计网络应用程序的API。RESTful API是基于REST原则设计的Web API。
核心原则
REST的六大约束
- 客户端-服务器分离: 客户端和服务器独立演化
- 无状态: 每个请求包含所有必要信息
- 可缓存: 响应必须明确是否可以缓存
- 统一接口: 使用标准HTTP方法
- 分层系统: 支持代理、网关等中间层
- 按需代码: 服务器可以临时扩展客户端功能
REST vs 传统API
特性 | 传统API | RESTful 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需要遵循以下原则:
- 资源导向: 使用名词而不是动词
- HTTP方法: 充分利用标准HTTP方法
- 无状态: 每个请求包含所有必要信息
- 统一接口: 保持API的一致性
- 错误处理: 使用标准HTTP状态码
- 版本控制: 合理管理API版本
- 安全考虑: 实施认证、授权和验证
- 性能优化: 使用缓存和分页
- 文档完善: 提供清晰的API文档
遵循这些原则可以设计出易用、可维护、高性能的RESTful API。