中间件
本教程共 76 篇 · 第 40 篇 · 更新于 2026-07-25 · 约 6 分钟阅读
40. 中间件
本节目标:四种中间件层级、执行顺序和 Express 5 原生异步错误捕获。
中间件(Middleware)是 Express 的灵魂。官方对它的定义很直白:中间件是能访问请求对象、响应对象和 next 函数的函数。说它「灵魂」,是因为 Express 自身几乎不做任何事——路由、body 解析、静态文件、错误处理,全是中间件在干活。
你可以把中间件想象成工厂流水线上的工位。一个请求进来,依次经过日志记录、身份校验、参数解析、业务处理,最后包装成响应返回。每个工位只做一件事,做完把工件传给下一站。
中间件的基本形态
一个最简中间件长这样:
function myMiddleware(req, res, next) {
console.log(`${req.method} ${req.url}`)
next() // 必须调用,否则请求卡死
}
app.use(myMiddleware)
app.use() 表示「所有请求都经过这个中间件」。如果你想限定路径:
// 只拦截 /api 开头的请求
app.use('/api', myMiddleware)
Express 5 对异步中间件做了重要改进:如果中间件返回一个 rejected Promise,错误会自动传给错误处理中间件,不再需要手动包 try/catch 或 next(err)。
// Express 5:Promise 被拒会自动进入错误处理中间件
app.get('/data', async (req, res) => {
const data = await fetchFromDatabase() // 如果抛错,自动走错误中间件
res.json(data)
})
NoteExpress 4 里异步函数抛错会导致请求挂起,必须用
try/catch或者express-async-handler这类库包裹。Express 5 终于原生支持了,这是升级 5 最爽的理由之一。
中间件的四种层级
1. 应用级中间件
绑定在 app 实例上,影响整个应用:
app.use(express.json()) // 解析 JSON body
app.use(express.urlencoded({ extended: true })) // 解析表单
app.use(express.static('public')) // 静态文件
2. 路由级中间件
绑定在 express.Router() 上,只影响该路由模块:
import express from 'express'
const router = express.Router()
router.use((req, res, next) => {
console.log('Users router time:', Date.now())
next()
})
router.get('/', (req, res) => res.json([]))
export default router
3. 路由特定中间件
只作用于某一条路由:
function requireAuth(req, res, next) {
if (!req.headers.authorization) {
return res.status(401).json({ error: 'Unauthorized' })
}
next()
}
app.post('/admin', requireAuth, (req, res) => {
res.json({ message: 'Admin only' })
})
你可以连续挂多个:
app.post('/users', validateUser, requireAuth, createUser)
4. 错误处理中间件
这是唯一一种带四个参数的中间件,Express 靠参数数量来识别它:
app.use((err, req, res, next) => {
console.error(err.stack)
res.status(err.status || 500).json({
error: process.env.NODE_ENV === 'production'
? 'Internal Server Error'
: err.message
})
})
Warning错误处理中间件必须放在所有其他
app.use()和路由之后。如果放在前面,它捕获不到后面抛出的错误。另外,四个参数缺一不可,哪怕next不用,也得写上,否则 Express 把它当成普通中间件。
常用内置中间件
Express 5 内置了几个最基础的中间件,开箱即用:
| 中间件 | 作用 | 示例 |
|---|---|---|
express.json() | 解析 Content-Type: application/json 的请求体 | app.use(express.json()) |
express.urlencoded() | 解析表单数据 | app.use(express.urlencoded({ extended: true })) |
express.static() | 托管静态文件 | app.use(express.static('public')) |
express.raw() | 解析原始二进制 | app.use(express.raw()) |
express.text() | 解析纯文本 | app.use(express.text()) |
express.urlencoded({ extended: true }) 里的 extended 选项控制解析器是否支持嵌套对象。设为 true 用 qs 库,能解析 a[b]=1 这种嵌套;设为 false 用原生 querystring,只支持平铺。一般保持 true 即可。
第三方中间件推荐
社区生态是 Express 最大的底气。下面这几个包我几乎每个项目都会装:
npm install morgan helmet cors compression
import morgan from 'morgan' // HTTP 请求日志
import helmet from 'helmet' // 安全响应头
import cors from 'cors' // 跨域
import compression from 'compression' // gzip 压缩
app.use(morgan('dev'))
app.use(helmet())
app.use(cors())
app.use(compression())
- morgan:开发时看请求日志很方便,
combined格式适合生产接日志分析系统 - helmet:帮你设好一堆安全头,防 XSS、点击劫持等,省得自己背参数
- cors:一键开启跨域,支持配置白名单
- compression:对响应体做 gzip,省带宽
自定义中间件实战
假设我们需要一个「请求计时」中间件,记录每个接口的处理耗时:
function requestTimer(req, res, next) {
const start = performance.now()
res.on('finish', () => {
const duration = (performance.now() - start).toFixed(2)
console.log(`${req.method} ${req.url} - ${res.statusCode} - ${duration}ms`)
})
next()
}
app.use(requestTimer)
这里用了 res.on('finish') 监听响应发送完毕的事件。注意中间件里不要做太重的事,否则每个请求都拖慢。
再写一个「统一响应格式」的中间件,给 res 挂一个快捷方法:
app.use((req, res, next) => {
res.sendSuccess = (data, message = 'ok') => {
res.json({ code: 0, message, data })
}
res.sendError = (message, code = 500) => {
res.status(code).json({ code, message, data: null })
}
next()
})
// 后续路由里可以这样用
app.get('/users', (req, res) => {
res.sendSuccess([{ id: 1, name: 'tom' }])
})
执行顺序:为什么我的中间件没生效
这是新手最高频的问题之一。Express 中间件按代码顺序执行,所以:
// 错误:body 还没解析,就读 req.body
app.post('/users', (req, res) => {
console.log(req.body) // undefined
})
app.use(express.json())
// 正确
app.use(express.json())
app.post('/users', (req, res) => {
console.log(req.body) // 正常
})
推荐顺序:
- 安全类(helmet、cors)
- 日志类(morgan)
- 解析类(json、urlencoded、static)
- 自定义全局中间件
- 路由
- 404 兜底
- 错误处理中间件