Node系列 · Express:中间件
中间件(Middleware)是 Express 的灵魂——每个请求都会经过一连串中间件处理。理解中间件的"链式调用"和
next()的控制权移交,就理解了 Express 90% 的工作方式。
一、什么是中间件
中间件是请求处理链上的一个环节——它能:
- 读取
req(请求对象)并加工 - 改写
res(响应对象) - 决定是否调用
next()移交控制权 - 提前
res.send()结束响应
二、三种中间件
2.1 普通中间件(3 参数)
javascript
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next(); // 必须调用,否则请求会卡住
}
app.use(logger);参数 (req, res, next)——next 是函数,调用它把控制权交给下一个中间件。
2.2 错误处理中间件(4 参数)
javascript
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: '服务器内部错误' });
});4 个参数(多一个 err)让 Express 把它识别为错误中间件。要触发错误中间件,需要在前面调 next(err)。
2.3 路由中间件(绑定到特定路径)
javascript
// 仅 /api/* 路径生效
app.use('/api', (req, res, next) => {
console.log('API 请求');
next();
});也可以直接用 app.get(path, handler) / app.post(path, handler)——本质也是中间件。
三、next() 的三种行为
3.1 正常 next
javascript
app.use((req, res, next) => {
console.log('中间件 A');
next(); // 移交给下一个
});3.2 next(err) 跳到错误处理
javascript
app.use((req, res, next) => {
if (!req.headers.authorization) {
return next(new Error('未授权'));
}
next();
});
app.use((err, req, res, next) => {
res.status(401).json({ error: err.message });
});3.3 不调 next(直接结束)
javascript
app.use((req, res, next) => {
if (req.path === '/favicon.ico') {
return res.status(204).end(); // 直接结束,不 next
}
next();
});WARNING
不调 next() 也不 res.send()——请求会卡住直到超时。Express 默认 0 字节响应 + 超时。一定要保证每个分支都有出口。
四、中间件顺序
Express 按 app.use() 注册顺序执行:
javascript
app.use(logger); // 1. 日志
app.use(express.json()); // 2. 解析 JSON body
app.use('/api', authCheck); // 3. /api/* 鉴权
app.use('/api/users', userRoutes); // 4. 用户路由
app.use(notFound); // 5. 404
app.use(errorHandler); // 6. 错误处理每条规则:
| 顺序 | 中间件 | 作用 |
|---|---|---|
| 1 | 日志 / 性能监控 | 记录每个请求 |
| 2 | body 解析 | express.json / express.urlencoded |
| 3 | CORS / 安全头 | 跨域、安全策略 |
| 4 | 会话 / 鉴权 | 检查登录状态 |
| 5 | 业务路由 | 实际 API |
| 6 | 404 处理 | 未匹配路径 |
| 7 | 错误处理 | 捕获前面抛出的异常 |
五、实战:日志中间件
javascript
function requestLogger(req, res, next) {
const start = Date.now();
// 在响应结束时打印耗时
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} ${res.statusCode} - ${duration}ms`);
});
next();
}
app.use(requestLogger);六、实战:鉴权中间件
javascript
function requireAuth(req, res, next) {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: '未登录' });
}
try {
const payload = jwt.verify(token, SECRET);
req.user = payload; // 挂到 req 上供后续中间件用
next();
} catch (err) {
next(err); // 触发错误中间件
}
}
// 用法:保护 /api/private/*
app.use('/api/private', requireAuth);七、async 中间件
Express 4.x 不会自动捕获 async 中间件抛出的错误。Express 5+ 才原生支持:
javascript
// Express 5(即将 GA):async 直接抛错会自动被错误中间件捕获
app.get('/api/users', async (req, res) => {
const users = await User.findAll();
res.json(users);
});
// Express 4.x:需要包装一层 catch
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
app.get('/api/users', asyncHandler(async (req, res) => {
const users = await User.findAll();
res.json(users);
}));八、最佳实践
| 场景 | 推荐 |
|---|---|
| 通用中间件 | 抽成单独文件,统一在 app.js 注册 |
| 鉴权中间件 | 挂在 /api/* 路径下,业务路由才生效 |
| 错误处理 | 始终放在最后注册 |
| async 中间件 | Express 5 直接写 async;4.x 用 wrapper |
| 第三方中间件 | 用 app.use() 引入前先看文档 |
| 调试中间件 | morgan(HTTP 日志)/ helmet(安全头) |
九、小结
- 中间件是请求处理链的一个环节;签名
(req, res, next) - 错误中间件特殊:4 个参数
(err, req, res, next) next()移交;next(err)跳错误处理;不调 next 直接 res.send 结束- 注册顺序就是执行顺序:日志 → body → CORS → 鉴权 → 路由 → 404 → 错误
- async 中间件:Express 5 原生支持;4.x 用
asyncHandlerwrapper - 中间件的本质是函数组合(chain of responsibility),是 Express 灵活性的来源
