Express 中间件怎么理解:执行顺序、分类、错误处理与常见陷阱
面试速答(30 秒版)
- Express 中间件本质上就是一组按顺序执行的函数,签名通常是
req, res, next。 - 它解决的是“请求到来后,哪些通用逻辑该按什么顺序被复用”,比如日志、鉴权、解析参数、跨域、限流。
- 普通中间件决定请求如何继续向后流转;错误中间件专门处理异常,签名是
err, req, res, next。 - 面试里最关键的不是背 API,而是说清楚 3 件事:顺序、作用域、何时结束响应。
- 一句话总结:Express 中间件是把横切逻辑从业务 handler 里抽出来,再串成一条可组合的处理链。
一、先别背定义,先理解它为什么存在
如果没有中间件,你的每个路由都要自己写:
- 打日志
- 解析 body
- 做权限校验
- 校验参数
- 统一处理错误
这样一来,代码会迅速变成复制粘贴。
Express 的思路是把这些公共逻辑抽出来,放到请求进入业务之前或之后统一处理。
可以把它想成:
这就是中间件最本质的价值:
- 把通用逻辑和业务逻辑分层。
二、Express 中间件到底长什么样
最常见的普通中间件:
app.use((req, res, next) => {
req.startTime = Date.now();
next();
});
这段代码要说明 3 个点:
req和res是当前请求的上下文。next()表示把控制权交给下一个匹配层。- 如果不
next(),也不返回响应,请求就会挂住。
错误处理中间件长这样:
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({ message: "server error" });
});
注意它和普通中间件最大的区别是多了第一个参数 err。
三、中间件有哪些分类
1. 按挂载方式分
全局中间件:
app.use(express.json());
特点:
- 所有匹配路径的请求都可能经过它。
路由级中间件:
router.get("/profile", authMiddleware, handler);
特点:
- 只对某个路由或某组路由生效。
2. 按职责分
常见类别:
- 日志
- body 解析
- cookie / session
- 鉴权
- 参数校验
- 限流
- 静态资源
- 错误处理
面试时建议补一句:
- 中间件分类不是框架硬性规定,而是工程分层习惯。
四、Express 中间件为什么强调顺序
Express 的执行模型是线性的,先注册谁,谁就先匹配。
比如:
app.use(authMiddleware);
app.use("/admin", adminRouter);
此时访问 /admin/users,会先走 authMiddleware,再进入 adminRouter。
但如果你把错误处理中间件放在前面:
app.use(errorHandler);
app.use("/api", router);
这个顺序通常就不对,因为错误处理中间件是拿来接住前面抛出的异常的,应该放在后面。
一个相对合理的顺序通常是:
- 请求标识、基础日志
- 限流、跨域、安全头
- body 解析、cookie 解析
- 鉴权
- 参数校验
- 业务路由
- 404 处理
- 错误处理中间件
五、路由和中间件是什么关系
很多人会把两者拆开讲,但在 Express 里,路由本质上也可以看成一种特殊处理层。
比如:
app.get("/users/:id", authMiddleware, getUserById);
这里本质发生了两件事:
- 先看 HTTP 方法和路径是否匹配。
- 匹配后依次执行挂在这条路由上的处理函数。
所以你可以把路由理解成:
- 带路径和方法条件的中间件链入口。
六、next() 到底做了什么
这是面试常问点。
在 Express 里,next() 的含义可以理解成:
- 当前层处理完了,继续交给后面的匹配层。
常见情况有 3 种:
next():进入下一个普通层。next(err):跳过普通层,进入错误处理中间件。- 不调用
next(),直接res.send()/res.json():当前链路在这里结束。
最容易出问题的是既响应又 next(),比如:
app.use((req, res, next) => {
res.json({ ok: true });
next();
});
这可能导致后续逻辑继续执行,触发重复写响应。
七、错误处理中间件怎么讲才完整
1. 它的职责不是“打印报错”这么简单
更准确的职责是:
- 统一转换错误结构
- 隐藏内部细节
- 记录日志
- 按不同错误类型返回不同状态码
2. 最好有统一错误模型
比如:
class AppError extends Error {
constructor(status, message) {
super(message);
this.status = status;
}
}
配合错误处理中间件:
app.use((err, req, res, next) => {
const status = err.status || 500;
res.status(status).json({
message: err.message || "Internal Server Error",
});
});
这样接口行为更可控。
八、中间件设计的工程原则
写得好的中间件一般符合这些特点:
- 单一职责,不要一个中间件里混日志、鉴权、参数校验。
- 尽量无副作用,副作用要明确。
- 尽量早失败,减少非法请求继续消耗资源。
- 约定好往
req上挂哪些字段,避免命名污染。 - 错误直接往统一错误流收敛,不要每层都手写响应。
面试里很实用的一句是:
- 中间件不是越多越好,而是越清晰、越可组合越好。
典型题与标准答法
1. Express 中间件是什么?
- 是一组在请求处理过程中按顺序执行的函数。
- 用来复用横切逻辑,比如日志、鉴权、解析和错误处理。
2. 为什么中间件顺序很重要?
- 因为 Express 按注册顺序匹配和执行。
- 顺序不对,可能导致鉴权失效、参数没解析、错误接不住。
3. next() 不调用会怎样?
- 如果当前中间件也没有结束响应,请求会一直挂住。
4. 错误处理中间件和普通中间件区别是什么?
- 错误处理中间件多一个
err参数。 - 它只在发生错误并进入错误流时执行。
常见追问
1. 中间件里能不能查数据库?
- 能,但要谨慎。
- 如果是每个请求都必须的数据,可以查;如果不是通用逻辑,最好留给业务层。
2. 中间件里往 req 上挂数据合理吗?
- 合理,但要控制命名和边界。
- 否则会变成隐式耦合。
3. 中间件和装饰器、AOP 有什么像的地方?
- 都是在主业务逻辑外围织入横切逻辑。
- 只是 Express 用的是请求链式处理模型。
易错点
- 只会说“中间件就是一个函数”,说不出它解决什么问题。
- 忘记错误处理中间件必须放在后面。
- 在中间件里发送响应后还继续
next()。 - 一个中间件做太多事,导致难测难复用。
- 把路由和中间件完全割裂看待。
速记要点
- Express 中间件 =
req, res, next的处理链。 - 核心是顺序、作用域、结束时机。
next()是继续流转,next(err)是进入错误流。- 路由可以看成带匹配条件的处理中间件层。
- 好的中间件设计强调单一职责和统一错误收敛。