中间件模式本质是显式调用 next() 的函数链,非装饰器语法糖;必须按关注点拆分、顺序注册、错误统一用 next(err) 传递,避免异步陷阱与流程中断。

中间件模式本质是函数链,不是装饰器套壳
中间件不是给路由加个 @auth_required 就算用了——那只是装饰器语法糖。真正的中间件模式要求每个处理单元接收 req、res 和 next,且必须显式调用 next() 才能进入下一环。漏掉 next() 或在异步操作里没等完就调 next(),流程直接中断或乱序。
常见错误现象:req.user 在后续中间件里是 undefined;日志中间件没打印出响应耗时;接口卡住无返回。
- 所有中间件函数必须是同步调用
next(),或在 Promise.then()/async/await之后再调 - 错误中间件必须有 4 个参数:
(err, req, res, next),少一个就不会被框架识别为错误处理器 - Express 中
app.use()注册的中间件对所有路径生效,router.use()才按前缀过滤
业务逻辑拆分要按“关注点”而非“功能模块”
别把“用户管理”打包成一个 userMiddleware,然后在里面塞校验、查库、权限、缓存更新……这违背中间件本意。应该按横切关注点切:比如 validateBody 只管字段格式,loadUser 只查一次 DB 并挂到 req.user,checkPermission 只比对角色和资源动作。
这样做的好处是复用性高、测试简单、调试定位快。坏处是初期文件略多,但比后期改一个需求要动五个地方强得多。
-
validateBody应该用joi或zod做 schema 校验,失败直接return res.status(400).json(...),不调next() -
loadUser如果查不到用户,应调next(new Error('User not found')),交给统一错误中间件处理,而不是自己res.status(404) - 不要在中间件里写业务主流程(如“创建订单”),那是控制器(Controller)的事;中间件只做前置准备或后置收尾
错误传递必须走 next(err),不能 throw 或 res.status(500)
在 Express 里 throw new Error() 看似能触发错误中间件,但实际不可靠:如果发生在事件循环微任务中(比如 Promise.then() 里),会变成未捕获异常,进程可能崩。而 res.status(500).send() 则彻底跳出中间件链,后续错误中间件完全失效。
唯一安全方式是显式调用 next(err),让框架按注册顺序把错误往下传,直到匹配到 4 参数中间件。
- 异步中间件中,
try/catch捕获后必须next(err),不能throw err - 自定义错误类(如
class ValidationError extends Error)可以带status属性,错误中间件里读取并设置res.status(err.status || 500) - 数据库连接失败、Redis 超时这类底层错误,建议包装成
new ServiceUnavailableError()再next(),避免暴露细节给前端
中间件顺序决定执行流,路径匹配和错误处理不能颠倒
Express 的中间件执行顺序严格依赖注册顺序。常见陷阱是把日志中间件放在最后,结果错误发生时根本没日志;或者把 bodyParser 放在自定义验证中间件后面,导致 req.body 还是空对象。
典型推荐顺序:bodyParser → cors → helmet → logger → auth → validate → loadEntity → checkPermission → controller → error handler。其中错误中间件必须用 app.use() 注册在所有路由之后,且不能带路径参数。
-
app.use('/api', router)是路由挂载点,不是中间件;它的上游中间件对/api下所有路径生效,但下游中间件(比如router.use())只对匹配路径生效 - 静态资源中间件(
express.static)建议放最前面,避免被鉴权中间件拦住 - 开发环境可加
morgan日志,但生产环境慎用详细日志中间件,I/O 开销明显
最容易被忽略的是:中间件链一旦开始执行,就无法“回退”或“跳过已注册的中间件”。所谓“跳过”,其实是条件判断后不调 next(),但这属于业务控制流,不是框架机制。真要动态启用/禁用,得用开关变量或配置驱动,而不是删中间件函数。










