Node系列 · Express:中间件
中间件(Middleware)是 Express 的灵魂------每个请求都会经过一连串中间件处理。理解中间件的"链式调用"和
next()的控制权移交,就理解了 Express 90% 的工作方式。
一、什么是中间件
中间件是请求处理链上的一个环节------它能:
- 读取
req(请求对象)并加工 - 改写
res(响应对象) - 决定是否调用
next()移交控制权 - 提前
res.send()结束响应
#mermaid-svg-P8tvvxE18tbVlpAc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-P8tvvxE18tbVlpAc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-P8tvvxE18tbVlpAc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-P8tvvxE18tbVlpAc .error-icon{fill:#552222;}#mermaid-svg-P8tvvxE18tbVlpAc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-P8tvvxE18tbVlpAc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-P8tvvxE18tbVlpAc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-P8tvvxE18tbVlpAc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-P8tvvxE18tbVlpAc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-P8tvvxE18tbVlpAc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-P8tvvxE18tbVlpAc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-P8tvvxE18tbVlpAc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-P8tvvxE18tbVlpAc .marker.cross{stroke:#333333;}#mermaid-svg-P8tvvxE18tbVlpAc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-P8tvvxE18tbVlpAc p{margin:0;}#mermaid-svg-P8tvvxE18tbVlpAc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-P8tvvxE18tbVlpAc .cluster-label text{fill:#333;}#mermaid-svg-P8tvvxE18tbVlpAc .cluster-label span{color:#333;}#mermaid-svg-P8tvvxE18tbVlpAc .cluster-label span p{background-color:transparent;}#mermaid-svg-P8tvvxE18tbVlpAc .label text,#mermaid-svg-P8tvvxE18tbVlpAc span{fill:#333;color:#333;}#mermaid-svg-P8tvvxE18tbVlpAc .node rect,#mermaid-svg-P8tvvxE18tbVlpAc .node circle,#mermaid-svg-P8tvvxE18tbVlpAc .node ellipse,#mermaid-svg-P8tvvxE18tbVlpAc .node polygon,#mermaid-svg-P8tvvxE18tbVlpAc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-P8tvvxE18tbVlpAc .rough-node .label text,#mermaid-svg-P8tvvxE18tbVlpAc .node .label text,#mermaid-svg-P8tvvxE18tbVlpAc .image-shape .label,#mermaid-svg-P8tvvxE18tbVlpAc .icon-shape .label{text-anchor:middle;}#mermaid-svg-P8tvvxE18tbVlpAc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-P8tvvxE18tbVlpAc .rough-node .label,#mermaid-svg-P8tvvxE18tbVlpAc .node .label,#mermaid-svg-P8tvvxE18tbVlpAc .image-shape .label,#mermaid-svg-P8tvvxE18tbVlpAc .icon-shape .label{text-align:center;}#mermaid-svg-P8tvvxE18tbVlpAc .node.clickable{cursor:pointer;}#mermaid-svg-P8tvvxE18tbVlpAc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-P8tvvxE18tbVlpAc .arrowheadPath{fill:#333333;}#mermaid-svg-P8tvvxE18tbVlpAc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-P8tvvxE18tbVlpAc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-P8tvvxE18tbVlpAc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P8tvvxE18tbVlpAc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-P8tvvxE18tbVlpAc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P8tvvxE18tbVlpAc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-P8tvvxE18tbVlpAc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-P8tvvxE18tbVlpAc .cluster text{fill:#333;}#mermaid-svg-P8tvvxE18tbVlpAc .cluster span{color:#333;}#mermaid-svg-P8tvvxE18tbVlpAc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-P8tvvxE18tbVlpAc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-P8tvvxE18tbVlpAc rect.text{fill:none;stroke-width:0;}#mermaid-svg-P8tvvxE18tbVlpAc .icon-shape,#mermaid-svg-P8tvvxE18tbVlpAc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-P8tvvxE18tbVlpAc .icon-shape p,#mermaid-svg-P8tvvxE18tbVlpAc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-P8tvvxE18tbVlpAc .icon-shape .label rect,#mermaid-svg-P8tvvxE18tbVlpAc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-P8tvvxE18tbVlpAc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-P8tvvxE18tbVlpAc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-P8tvvxE18tbVlpAc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} next
next
不 next 直接 res.send
HTTP 请求
中间件 1
日志
中间件 2
解析 body
路由处理
业务
HTTP 响应
二、三种中间件
2.1 普通中间件(3 参数)
javascript:normal-middleware.js
function logger(req, res, next) {
console.log(`${req.method} ${req.url}`);
next(); // 必须调用,否则请求会卡住
}
app.use(logger);
参数 (req, res, next)------next 是函数,调用它把控制权交给下一个中间件。
2.2 错误处理中间件(4 参数)
javascript:error-middleware.js
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ error: '服务器内部错误' });
});
4 个参数 (多一个 err)让 Express 把它识别为错误中间件。要触发错误中间件,需要在前面调 next(err)。
2.3 路由中间件(绑定到特定路径)
javascript:route-middleware.js
// 仅 /api/* 路径生效
app.use('/api', (req, res, next) => {
console.log('API 请求');
next();
});
也可以直接用 app.get(path, handler) / app.post(path, handler)------本质也是中间件。
三、next() 的三种行为
3.1 正常 next
javascript:normal-middleware.js
app.use((req, res, next) => {
console.log('中间件 A');
next(); // 移交给下一个
});
3.2 next(err) 跳到错误处理
javascript:error-middleware.js
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:terminate-middleware.js
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:middleware-order.js
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:custom-logger.js
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:auth-middleware.js
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:async-middleware.js
// 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 灵活性的来源