Node.js: 路由与中间件
最后更新:2026-08-26
Charlie 的 API 项目最初只有 5 个路由,全部写在 app.js 里还算清晰。两个月后路由增长到 30 个,文件超过 500 行,改一个接口要翻半天。团队决定用 express.Router() 把路由按模块拆分——用户路由归 users.js,认证路由归 auth.js,自定义日志中间件抽到独立文件。拆分后每个文件职责单一,维护效率大幅提升。
你将学到:
- 用
express.Router()拆分模块化路由 - 路由参数、查询字符串、请求体的读取方式
- 自定义中间件的编写与
next()机制 - 第三方中间件(cors / morgan / helmet)的集成
- 错误处理中间件的设计与全局错误捕获
1. 1 express.Router() 模块化路由
当路由数量增多,把所有路由堆在 app.js 里会导致文件臃肿、难以协作。express.Router() 可以创建独立的路由实例,再挂载到应用的某个路径前缀上。
| 特性 | app 直接定义路由 | Router 模块化路由 |
|---|---|---|
| 文件组织 | 全部在 app.js | 按模块拆分到独立文件 |
| 路径前缀 | 每条路由写完整路径 | 挂载时统一设置前缀 |
| 团队协作 | 多人改同一文件 | 各模块独立维护 |
| 可复用性 | 低 | 高,可跨项目复用 |
▶ 示例:创建独立的用户路由模块
// routes/users.js
const express = require('express');
const router = express.Router();
router.get('/', (req, res) => {
res.json({ users: ['Alice', 'Bob'] });
});
router.get('/:id', (req, res) => {
res.json({ user: req.params.id });
});
module.exports = router;
▶ 示例:在 app.js 中挂载路由模块
// app.js
const express = require('express');
const app = express();
const userRouter = require('./routes/users');
app.use('/users', userRouter);
app.listen(3000, () => {
console.log('Server running on port 3000');
});
访问 /users 匹配 router.get('/'),访问 /users/42 匹配 router.get('/:id')。
2. 2 路由参数、查询字符串与请求体
Express 提供三种方式接收客户端数据,适用场景各不相同。
| 数据来源 | 获取方式 | URL 示例 | 典型用途 |
|---|---|---|---|
| 路由参数 | req.params |
/users/42 |
标识具体资源 |
| 查询字符串 | req.query |
/users?role=admin |
过滤、排序、搜索 |
| 请求体 | req.body |
POST body | 提交表单/JSON 数据 |
▶ 示例:路由参数 :id
app.get('/products/:id', (req, res) => {
const productId = req.params.id;
res.json({ productId });
});
▶ 示例:查询字符串 req.query
app.get('/products', (req, res) => {
const { category, sort, page } = req.query;
res.json({ category, sort, page: page || 1 });
});
访问 /products?category=electronics&sort=price&page=2,req.query 为 { category: 'electronics', sort: 'price', page: '2' }。
▶ 示例:请求体 req.body 与 express.json()
app.use(express.json());
app.post('/products', (req, res) => {
const { name, price } = req.body;
res.status(201).json({ name, price });
});
注意:
req.query和req.params的值都是字符串类型,需要手动转换数字。
3. 3 中间件基础与 next() 机制
中间件是 Express 的核心概念——每个请求都经过一条中间件链,每个中间件可以读取请求、修改响应,或把控制权交给下一个中间件。
graph LR A[Request] --> B[morgan 日志] B --> C[express.json 解析] C --> D[自定义 auth 中间件] D --> E[路由处理函数] E --> F[Response] D -->|next error| G[错误处理中间件]
▶ 示例:最简单的自定义中间件
function logger(req, res, next) {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
}
app.use(logger);
| 规则 | 说明 |
|---|---|
必须调用 next() |
否则请求挂起,客户端永远收不到响应 |
| 调用顺序即执行顺序 | 先注册的中间件先执行 |
app.use() 全局生效 |
app.use('/api', ...) 只对 /api 路径生效 |
next('route') |
跳过当前路由的剩余中间件 |
4. 4 第三方中间件集成
社区提供了大量现成中间件,安装后一行代码即可启用。
| 中间件 | 用途 | 安装命令 |
|---|---|---|
| cors | 解决跨域请求问题 | npm install cors |
| morgan | HTTP 请求日志记录 | npm install morgan |
| helmet | 设置安全响应头 | npm install helmet |
| express-rate-limit | 请求频率限制 | npm install express-rate-limit |
| cookie-parser | 解析 Cookie | npm install cookie-parser |
▶ 示例:集成 cors、morgan、helmet
const cors = require('cors');
const morgan = require('morgan');
const helmet = require('helmet');
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
提示: 安全相关中间件(helmet)建议放在最前面,日志中间件紧随其后。
5. 5 错误处理中间件
Express 约定:拥有 4 个参数 (err, req, res, next) 的函数就是错误处理中间件。只要任意中间件调用 next(err),就会跳过后续普通中间件,直接进入错误处理中间件。
| 对比项 | 普通中间件 | 错误处理中间件 |
|---|---|---|
| 参数个数 | 3 个 (req, res, next) | 4 个 (err, req, res, next) |
| 触发方式 | 请求到达时按序执行 | next(err) 触发 |
| 注册位置 | 任意位置 | 必须放最后 |
| 能否多个 | 可以 | 可以,按序执行 |
▶ 示例:全局错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
});
▶ 示例:在路由中触发错误
app.get('/admin', (req, res, next) => {
const err = new Error('Access denied');
err.status = 403;
next(err);
});
6. 6 Mermaid:Router 模块化路由结构
graph TD APP[app.js] --> UR["/users → userRouter"] APP --> AR["/auth → authRouter"] APP --> MW["middleware/logger.js"] UR --> U1["GET / → 用户列表"] UR --> U2["GET /:id → 用户详情"] UR --> U3["POST / → 创建用户"] AR --> A1["POST /login → 登录"] AR --> A2["POST /register → 注册"] MW --> ML["日志中间件"]
7. 7 综合示例:模块化路由 API
本示例把用户路由、认证路由、日志中间件拆到独立文件,最终在 app.js 中整合。
▶ 示例:middleware/logger.js — 自定义日志中间件
function logger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.originalUrl} ${res.statusCode} ${duration}ms`);
});
next();
}
module.exports = logger;
▶ 示例:routes/users.js — 用户路由模块
const express = require('express');
const router = express.Router();
let users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
router.get('/', (req, res) => {
res.json(users);
});
router.get('/:id', (req, res, next) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) {
const err = new Error('User not found');
err.status = 404;
return next(err);
}
res.json(user);
});
router.post('/', (req, res) => {
const { name } = req.body;
if (!name) {
return res.status(400).json({ error: 'Name is required' });
}
const newUser = { id: users.length + 1, name };
users.push(newUser);
res.status(201).json(newUser);
});
module.exports = router;
▶ 示例:routes/auth.js — 认证路由模块
const express = require('express');
const router = express.Router();
router.post('/login', (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ error: 'Username and password required' });
}
res.json({ message: 'Login successful', token: 'mock-jwt-token' });
});
router.post('/register', (req, res) => {
const { username, email, password } = req.body;
if (!username || !email || !password) {
return res.status(400).json({ error: 'All fields are required' });
}
res.status(201).json({ message: 'Registration successful' });
});
module.exports = router;
▶ 示例:app.js — 整合入口
const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const morgan = require('morgan');
const logger = require('./middleware/logger');
const userRouter = require('./routes/users');
const authRouter = require('./routes/auth');
const app = express();
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(logger);
app.use('/users', userRouter);
app.use('/auth', authRouter);
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(err.status || 500).json({
error: err.message || 'Internal Server Error'
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
运行测试:
node app.js
Server running on port 3000
GET /users 200 3ms
GET /users/1 200 2ms
POST /auth/login 200 5ms
❓ 常见问题
📖 小节
- 1 express.Router() 模块化路由的核心概念与使用方法
- 2 路由参数、查询字符串与请求体的核心概念与使用方法
- 3 中间件基础与 next() 机制的核心概念与使用方法
- 4 第三方中间件集成的核心概念与使用方法
- 5 错误处理中间件的核心概念与使用方法
- 6 Mermaid:Router 模块化路由结构的核心概念与使用方法
- 7 综合示例:模块化路由 API的核心概念与使用方法
📝 作业
- 完成本课所有代码示例,确保每个示例都能正确运行
- 修改综合示例,添加自己的扩展功能
- 查阅官方文档,找出本课未涉及的1-2个API并编写测试代码
- 思考:在实际项目中,你会如何应用本课学到的知识?
- 尝试将本课知识与前面课程的内容结合,构建一个小项目