Node.js RESTful API 设计最佳实践
概述
RESTful API 是现代 Web 服务的标准架构风格。本文基于 Express/Koa 框架,介绍如何设计规范、易扩展的 RESTful API 接口。
REST 原则
资源命名
使用名词而非动词:
# 正确
GET /users
GET /users/123
POST /users
PUT /users/123
DELETE /users/123
# 错误
GET /getUsers
GET /getUserById/123
POST /createUserHTTP 方法对应
| 方法 | 操作 | 幂等性 | 安全性 |
|---|---|---|---|
| GET | 查询 | ✓ | ✓ |
| POST | 创建 | ✗ | ✗ |
| PUT | 完整更新 | ✓ | ✗ |
| PATCH | 部分更新 | ✓ | ✗ |
| DELETE | 删除 | ✓ | ✗ |
项目结构
src/
├── routes/ # 路由定义
├── controllers/ # 控制器
├── services/ # 业务逻辑
├── models/ # 数据模型
├── middlewares/ # 中间件
├── utils/ # 工具函数
└── config/ # 配置文件基础框架搭建
Express 框架
javascript
// app.js
const express = require('express')
const cors = require('cors')
const helmet = require('helmet')
const app = express()
// 安全中间件
app.use(helmet())
app.use(cors())
// 请求体解析
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
// 路由
app.use('/api/users', require('./routes/users'))
module.exports = appKoa 框架
javascript
// app.js
const Koa = require('koa')
const bodyParser = require('koa-bodyparser')
const cors = require('@koa/cors')
const app = new Koa()
app.use(bodyParser())
app.use(cors())
app.use(require('./routes/users').routes())
module.exports = app路由设计
用户路由示例
javascript
// routes/users.js
const express = require('express')
const router = express.Router()
const userController = require('../controllers/userController')
const { validate } = require('../middlewares/validation')
// 获取用户列表
router.get('/', userController.getUsers)
// 获取单个用户
router.get('/:id', userController.getUserById)
// 创建用户
router.post('/', validate('createUser'), userController.createUser)
// 更新用户
router.put('/:id', validate('updateUser'), userController.updateUser)
// 删除用户
router.delete('/:id', userController.deleteUser)
module.exports = router控制器实现
用户控制器
javascript
// controllers/userController.js
const userService = require('../services/userService')
exports.getUsers = async (req, res) => {
try {
const { page = 1, limit = 10 } = req.query
const users = await userService.getUsers({ page, limit })
res.json({
code: 200,
data: users,
message: 'success'
})
} catch (error) {
res.status(500).json({
code: 500,
message: error.message
})
}
}
exports.getUserById = async (req, res) => {
try {
const { id } = req.params
const user = await userService.getUserById(id)
if (!user) {
return res.status(404).json({
code: 404,
message: '用户不存在'
})
}
res.json({
code: 200,
data: user,
message: 'success'
})
} catch (error) {
res.status(500).json({
code: 500,
message: error.message
})
}
}错误处理
统一错误响应
javascript
// utils/response.js
class ApiError extends Error {
constructor(code, message) {
super(message)
this.code = code
this.name = 'ApiError'
}
}
const errorTypes = {
BAD_REQUEST: new ApiError(400, '请求参数错误'),
UNAUTHORIZED: new ApiError(401, '未授权'),
FORBIDDEN: new ApiError(403, '禁止访问'),
NOT_FOUND: new ApiError(404, '资源不存在'),
INTERNAL_ERROR: new ApiError(500, '服务器内部错误')
}
module.exports = { ApiError, errorTypes }错误处理中间件
javascript
// middlewares/errorHandler.js
const { ApiError } = require('../utils/response')
module.exports = (err, req, res, next) => {
if (err instanceof ApiError) {
return res.status(err.code).json({
code: err.code,
message: err.message
})
}
console.error('Server Error:', err)
res.status(500).json({
code: 500,
message: '服务器内部错误'
})
}数据验证
Joi 验证示例
javascript
// middlewares/validation.js
const Joi = require('joi')
const schemas = {
createUser: Joi.object({
name: Joi.string().required().min(2).max(50),
email: Joi.string().email().required(),
password: Joi.string().required().min(6)
}),
updateUser: Joi.object({
name: Joi.string().min(2).max(50),
email: Joi.string().email(),
password: Joi.string().min(6)
}).min(1)
}
const validate = (schemaName) => {
return (req, res, next) => {
const schema = schemas[schemaName]
const { error, value } = schema.validate(req.body)
if (error) {
return res.status(400).json({
code: 400,
message: error.details[0].message
})
}
req.body = value
next()
}
}
module.exports = { validate, schemas }认证授权
JWT 认证
javascript
// middlewares/auth.js
const jwt = require('jsonwebtoken')
const auth = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1]
if (!token) {
return res.status(401).json({
code: 401,
message: '未提供认证令牌'
})
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET)
req.user = decoded
next()
} catch (error) {
res.status(401).json({
code: 401,
message: '无效的认证令牌'
})
}
}
module.exports = auth权限控制
javascript
// middlewares/rbac.js
const rolePermissions = {
admin: ['*'],
user: ['user:read', 'user:update'],
guest: ['user:read']
}
const checkPermission = (requiredPermission) => {
return (req, res, next) => {
const userRole = req.user?.role || 'guest'
const permissions = rolePermissions[userRole] || []
if (permissions.includes('*') || permissions.includes(requiredPermission)) {
next()
} else {
res.status(403).json({
code: 403,
message: '权限不足'
})
}
}
}
module.exports = { checkPermission }分页与过滤
分页实现
javascript
// services/userService.js
exports.getUsers = async ({ page, limit }) => {
const offset = (page - 1) * limit
const [users, total] = await Promise.all([
User.find().skip(offset).limit(limit),
User.countDocuments()
])
return {
list: users,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
totalPages: Math.ceil(total / limit)
}
}
}日志记录
Morgan 日志
javascript
const morgan = require('morgan')
// 自定义格式
const format = ':method :url :status :response-time ms'
app.use(morgan(format))总结
设计良好的 RESTful API 需要遵循以下原则:
- 一致性 - 统一的接口风格和响应格式
- 可预测性 - 符合 REST 规范的资源操作
- 安全性 - 完善的认证授权机制
- 可扩展性 - 模块化的代码结构
- 可调试性 - 清晰的日志记录
