先记住这个答案
app.use()向Express应用实例添加中间件,可匹配应用下所有请求或指定前缀路径;router.use()向路由实例添加中间件,只有被app.use()挂载到某路径后,才会处理匹配请求。这个最根本差异是作用域范围不同,router内部路径前缀会与挂载点拼接,并且中间件无法通过router.use对外部请求生效。
app.use()作用于应用全局,router.use()限定于该路由器内部。router.use()的路径相对于路由器挂载点,而非应用根路径。- 用
router隔离中间件可避免影响其他路由,是模块化常用手段。
挂载机制:从应用层到路由器层
app.use()直接注册到Express应用对象的中间件列表中,任何请求只要路径前缀匹配(默认为/)就进入它不区分HTTP方法,适用于日志、CORS、静态资源等全局功能。router.use()注册到某个express.Router()实例的队列里,但该路由器本身必须被挂载到应用上才会生效,例如app.use('/api', router)。
重点在于路径基准:router.use('/auth')匹配的是当路由器被挂到前缀后,最终请求路径中相对该前缀的部分以/auth开头。假设app.use('/api', router),则/api/auth/login会触发该中间件。同一中间件如果直接用app.use('/auth')则只匹配/auth...。这种相对解析让路由器可以整体搬迁而不改内部路径。
执行顺序也有差异:请求先经过应用层中在挂载位置之前的全部中间件,再进入路由器,这时才轮到router.use()注册的中间件。一个路由器内部的中间件不会跑到外部,也不会影响其他路由器的请求,实现了中间件作用域的隔离。
不同子模块需要不同鉴权中间件
假设服务有/admin(后台管理)和/user(用户接口)两块,只允许管理员访问前者、只允许登录用户访问后者,若用app.use()注册adminAuth则会对所有请求调用,造成逻辑混乱。分别创建adminRouter和userRouter,各自adminRouter.use(requireAdmin)、userRouter.use(requireLogin)。
接着app.use('/admin', adminRouter)、app.use('/user', userRouter)。这样POST /admin/delete只走requireAdmin,GET /user/profile只走requireLogin。后续新增/public路由时,完全不会受到这些中间件影响。若后来把admin路径改成/management,只需改app.use一行,adminRouter内部所有use路径都不用变,因为它们都相对于路由器挂载点。
失效边界与排查代价
最容易犯错的是忘记路径前缀的相对性。例如在adminRouter里写了adminRouter.use('/admin/check'),实际生效为/admin/admin/check,请求永远匹配不到。要修正成/check或去掉前缀。另外,如果router.use()不传路径,它匹配该路由器处理的所有请求,这一点容易误当成全局匹配。
另一个边界是多个路由器挂载在同一前缀时,先挂载者优先处理,当路由器内部调用next()且没有匹配路由时,控制权会退出该路由器并回到应用外层。这意味着路由器内部的中间件无法拦截后续其他路由器的请求,隔离性虽好,但无法跨模块共享逻辑,如需公共逻辑应放到app层。排查这类问题时,要检查请求最终流经的中间件列表,并按路径拼接逻辑计算实际前缀。
容易答错的地方
- 对路径前缀使用绝对路径
- 误以为
router.use('/a')能匹配外层应用的根路径/a,实际它匹配相对该router挂载点的/a。例如router挂载在/api,实际路径是/api/a。纠正方法是设计router内部路径时只写相对部分。 - 认为可以全局访问router里挂载的中间件
- 有人试图在router.use中注册CORS希望影响所有接口,但实际只对该router生效。若想全局处理需使用
app.use()。在多层嵌套中经常混淆,应从中间件实际成员链判断作用范围。
面试官还会怎么问?
app.use()和router.use()挂载中间件在顺序上有什么区别?
app.use()按注册顺序执行,只要路径前缀匹配就先执行再进入下一个。router.use()只有在请求进入该router后才执行,且执行顺序相对于router内部注册顺序,router内部执行完后如果没res.send则跳出router继续外层app后续中间件。
如果router.use中调用了next('route')会怎样?
在中间件里没有'route'语义,next('route')一般用于路由处理方法跳过后续回调,在router.use里传入必须为错误对象,若传字符串'route'会被当成错误交给错误处理中间件,而不是跳过router。
多个router挂载同一路径时,中间件会互相干扰吗?
不会。每个router拥有独立中间件栈,请求按挂载顺序先进入第一个router,若匹配处理则结束,否则next()后进入第二个router。各router内的use不会对内层其他router生效。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。