Express 是 Node.js 生态中最主流的轻量级 Web 框架,而中间件(Middleware) 是 Express 的核心设计灵魂。它将 HTTP 请求的处理流程拆解为一系列可插拔、可复用的函数单元,实现了逻辑解耦、功能扩展和流程控制,是 Express 「一切皆中间件」设计理念的核心体现。
一、中间件核心概念
1. 定义
中间件是能够访问请求对象(req) 、响应对象(res) ,以及请求 - 响应周期中 next 回调函数的函数。它可以在请求到达路由处理函数之前、响应返回客户端之前执行任意逻辑,是请求处理流程中的「过滤器 / 处理器」。
2. 核心三要素
req:HTTP 请求对象,包含请求参数、请求头、请求体、Cookie 等所有请求信息res:HTTP 响应对象,用于向客户端返回状态码、响应头、响应体等next:回调函数,调用后将控制权交给当前栈中下一个匹配的中间件
3. 执行模型
Express 中间件采用线性顺序执行模型 (区别于 Koa 的洋葱模型):请求到达后,严格按照中间件的定义顺序和路径匹配规则依次执行,直到某个中间件结束响应(如 res.send()),或触发错误进入错误处理中间件。 若需要实现「后置处理逻辑」,通常通过监听 res 的 finish 事件实现。
二、五大类中间件与用法
Express 中间件按作用范围和功能分为 5 大类:应用级、路由级、错误处理、内置、第三方。
1. 应用级中间件
绑定到 express 实例(app 对象)上的中间件,通过 app.use() 和 app.METHOD() 定义,作用于全局应用。
(1)全局无路径中间件
所有请求都会触发,无路径限制。
javascript
运行
const express = require('express');
const app = express();
// 全局日志中间件
app.use((req, res, next) => {
console.log(`[${new Date().toLocaleString()}] ${req.method} ${req.path}`);
next(); // 必须调用,否则请求会挂起无响应
});
app.get('/', (req, res) => res.send('Hello World'));
app.listen(3000);
(2)指定路径的应用级中间件
仅当请求路径以指定路径为前缀时才执行。
javascript
运行
// 仅 /user 前缀的请求会触发
app.use('/user', (req, res, next) => {
console.log('访问用户模块');
next();
});
// 匹配 /user/info、/user/123 等路径
app.get('/user/info', (req, res) => res.send('用户信息'));
(3)多中间件串联
同一路径可挂载多个中间件,支持连续传参或数组形式。
javascript
运行
// 连续传入
app.use('/admin',
(req, res, next) => { console.log('身份校验'); next(); },
(req, res, next) => { console.log('权限校验'); next(); }
);
// 数组形式(适合模块化复用)
const verifyIdentity = (req, res, next) => next();
const verifyPermission = (req, res, next) => next();
app.use('/admin', [verifyIdentity, verifyPermission]);
2. 路由级中间件
绑定到 express.Router() 实例上的中间件,语法与应用级完全一致,核心作用是模块化拆分路由,适配大型项目。
javascript
运行
const express = require('express');
const app = express();
const userRouter = express.Router(); // 创建路由实例
// 路由级中间件:仅作用于 userRouter 下的所有路由
userRouter.use((req, res, next) => {
console.log('用户模块请求拦截');
next();
});
// 路由定义
userRouter.get('/info', (req, res) => res.send('用户信息'));
userRouter.post('/login', (req, res) => res.send('登录'));
// 将路由挂载到应用的 /user 路径下
app.use('/user', userRouter);
app.listen(3000);
3. 错误处理中间件
专门用于统一捕获和处理请求流程中的异常,必须定义 4 个参数 :err, req, res, next。Express 通过参数数量识别错误处理中间件,缺少任意一个参数都会失效。
- 触发方式:普通中间件 / 路由中调用
next(err)手动传递错误;同步代码抛出的错误会被自动捕获。 - 放置规则:必须放在所有普通中间件和路由之后。
javascript
运行
// 业务路由
app.get('/error', (req, res, next) => {
try {
throw new Error('参数校验失败');
} catch (err) {
err.status = 400;
next(err); // 将错误传递给错误处理中间件
}
});
// 全局错误处理中间件(4个参数缺一不可)
app.use((err, req, res, next) => {
console.error('错误堆栈:', err.stack);
const statusCode = err.status || 500;
res.status(statusCode).json({
code: statusCode,
message: err.message || '服务器内部错误'
});
});
注意:异步代码(Promise、async/await)中的错误无法自动捕获,必须手动
try/catch后调用next(err)传递,否则会导致进程崩溃。
4. 内置中间件
Express 4.16.0 之后内置了高频中间件,无需额外 npm 安装:
表格
| 内置中间件 | 功能说明 |
|---|---|
express.static |
托管静态资源(图片、CSS、JS、文件等) |
express.json |
解析 application/json 格式请求体,结果挂载到 req.body |
express.urlencoded |
解析 application/x-www-form-urlencoded 表单请求体 |
express.raw |
解析二进制请求体,返回 Buffer 格式 |
express.text |
解析纯文本格式请求体 |
常用示例
javascript
运行
// 1. 静态资源服务:访问 /static/xxx 读取 public 目录下文件
app.use('/static', express.static('public'));
// 2. 解析 JSON 请求体(POST/PUT 接口必备)
app.use(express.json());
// 3. 解析表单提交;extended: false 用 querystring,true 用 qs 支持嵌套对象
app.use(express.urlencoded({ extended: false }));
// 测试:POST 提交数据可通过 req.body 获取
app.post('/api/data', (req, res) => {
res.json({ received: req.body });
});
5. 第三方中间件
社区生态提供的功能中间件,是 Express 扩展性的核心来源,通过 npm 安装后引入使用。
高频第三方中间件
-
morgan:HTTP 请求日志记录
bash
运行
npm install morganjavascript
运行
const morgan = require('morgan'); app.use(morgan('dev')); // dev:简洁彩色日志;combined:完整标准日志 -
cors:跨域资源共享处理
bash
运行
npm install corsjavascript
运行
const cors = require('cors'); // 全局开启跨域 app.use(cors()); // 精细化配置 app.use(cors({ origin: 'https://example.com', credentials: true })); -
helmet:安全加固,自动设置 HTTP 安全响应头
bash
运行
npm install helmetjavascript
运行
const helmet = require('helmet'); app.use(helmet()); // 自动启用 XSS 防护、HSTS 等十余项安全策略 -
cookie-parser :解析 Cookie,结果挂载到
req.cookiesbash
运行
npm install cookie-parserjavascript
运行
const cookieParser = require('cookie-parser'); app.use(cookieParser('签名密钥')); // 支持签名 Cookie 防篡改 -
express-session:服务端会话管理
bash
运行
npm install express-sessionjavascript
运行
const session = require('express-session'); app.use(session({ secret: '加密密钥', resave: false, saveUninitialized: true, cookie: { maxAge: 24 * 60 * 60 * 1000 } // 会话有效期1天 })); -
multer :处理
multipart/form-data格式的文件上传
三、核心执行机制与 next () 用法
1. next () 的三种调用方式
表格
| 写法 | 作用 |
|---|---|
next() |
正常移交控制权,执行下一个匹配的中间件 |
next('route') |
跳过当前路由的剩余中间件,直接进入下一个同路径路由(仅对 app.METHOD() 生效) |
next(err) |
抛出错误,跳过后续所有普通中间件,直接进入错误处理中间件 |
next ('route') 示例
javascript
运行
app.get('/test',
(req, res, next) => {
if (req.query.skip === '1') return next('route'); // 跳过后续中间件
next();
},
(req, res) => res.send('执行了第二个中间件')
);
// 第二个同路径路由
app.get('/test', (req, res) => {
res.send('进入了下一个路由处理');
});
2. 匹配与执行规则
- 顺序优先:严格按照代码定义顺序执行,先定义先匹配。
- 前缀匹配(use) :
app.use('/api', fn)会匹配所有以/api开头的路径(/api、/api/user、/api/user/123)。 - 精确匹配(METHOD) :
app.get()、app.post()等方法级中间件,需同时匹配路径和 HTTP 方法。 - 响应终止原则 :一旦执行
res.send()、res.json()、res.end()等结束响应的方法,后续中间件逻辑不再生效;若仍尝试发送响应,会抛出Cannot set headers after they are sent to the client错误。
四、自定义中间件实战
1. 接口响应耗时统计
javascript
运行
app.use((req, res, next) => {
const startTime = Date.now();
// 监听响应完成事件,实现后置统计
res.on('finish', () => {
const duration = Date.now() - startTime;
console.log(`${req.method} ${req.originalUrl} | ${res.statusCode} | ${duration}ms`);
});
next();
});
2. 登录权限校验中间件
javascript
运行
// 抽离为独立模块,按需复用
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ code: 401, message: '未携带登录凭证' });
}
// Token 校验逻辑...
req.user = { id: 1, username: 'admin' }; // 校验通过后挂载用户信息
next();
};
// 仅需要登录的接口使用
app.get('/user/profile', authMiddleware, (req, res) => {
res.json({ user: req.user });
});
3. 异步中间件的正确写法
javascript
运行
// 异步中间件必须捕获异常并传递给 next
const asyncMiddleware = (req, res, next) => {
(async () => {
try {
await db.query('SELECT * FROM users');
next();
} catch (err) {
next(err);
}
})();
};
五、最佳实践与常见坑点
1. 全局中间件推荐顺序
合理的顺序能避免很多异常问题,建议遵循以下排列:
- 安全类中间件(helmet、cors)
- 日志中间件(morgan)
- 请求体解析中间件(json、urlencoded)
- Cookie / Session 解析中间件
- 静态资源托管
- 业务路由
- 404 兜底处理
- 全局错误处理中间件
2. 常见坑点
- 忘记调用
next():请求会一直挂起,最终超时无响应。 - 错误处理中间件参数缺失:4 个参数缺一不可,少参数会被识别为普通中间件。
- 异步错误未捕获 :async 函数、Promise 中的错误必须手动
try/catch后next(err)。 - 重复发送响应 :多个中间件中都调用
res.send()会触发响应头重复设置错误。 - 路径匹配混淆 :
use是前缀匹配,get/post是精确路径匹配,使用时注意区分。
3. 工程化建议
大型项目中建议将中间件抽离为独立模块(如 middleware/logger.js、middleware/auth.js),按需引入,避免主文件臃肿;通用中间件统一在入口文件注册,业务中间件局部挂载。