Skip to content
AskSoul

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 /createUser

HTTP 方法对应

方法操作幂等性安全性
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 = app

Koa 框架

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 需要遵循以下原则:

  1. 一致性 - 统一的接口风格和响应格式
  2. 可预测性 - 符合 REST 规范的资源操作
  3. 安全性 - 完善的认证授权机制
  4. 可扩展性 - 模块化的代码结构
  5. 可调试性 - 清晰的日志记录