3、中间件机制:中间件概念、应用级中间件、路由级中间件、错误处理中间件、内置中间件(express.static)
说到 Express,我最想跟你聊的就是中间件。这玩意儿说白了,就是 Express 的灵魂。没有中间件,Express 就是个空壳子。
我记得刚接触 Node.js 那会儿,对中间件的理解特别肤浅——不就是个函数吗?后来在项目中踩了不少坑,才真正明白它的威力。今天咱们就把这块掰开揉碎了讲清楚。
3.1 中间件到底是什么?
中间件,英文叫 Middleware。你可以把它想象成一道流水线上的工人。
每个请求进来,就像一件待加工的产品。它要经过一道道工序:有人负责检查身份(认证中间件),有人负责记录日志(日志中间件),有人负责解析数据(body-parser),最后才到真正的业务处理。
每个中间件都能做三件事:
- 执行一些代码
- 修改请求和响应对象
- 结束请求-响应循环
- 调用下一个中间件
嗯,这里要注意——如果你不调用 next(),请求就卡在那儿了。我刚开始写代码时就犯过这错,页面一直转圈,查了半天才发现是忘了调 next。
核心概念:中间件函数的签名是 (req, res, next)。req 是请求对象,res 是响应对象,next 是下一个中间件函数。
3.2 应用级中间件
应用级中间件,就是绑定在 app 实例上的中间件。用 app.use() 或 app.METHOD() 来挂载。
我个人习惯把全局通用的中间件都放在入口文件最前面。比如日志记录、CORS 处理、请求体解析这些。
const express = require('express');
const app = express();
// 应用级中间件 - 记录请求日志
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next(); // 别忘了调 next
});
// 应用级中间件 - 解析 JSON 请求体
app.use(express.json());
// 应用级中间件 - 只对 /api 开头的路由生效
app.use('/api', (req, res, next) => {
console.log('API 请求来了');
next();
});
app.get('/hello', (req, res) => {
res.send('Hello World');
});
app.listen(3000);
你想想看,如果每个路由都写一遍日志逻辑,那得多痛苦?应用级中间件就是用来解决这种重复劳动的。
我的经验:应用级中间件的顺序很重要。比如 CORS 中间件一定要放在最前面,否则跨域请求在早期就被拦截了。我曾经因为这个排查了整整一下午。
3.3 路由级中间件
路由级中间件跟应用级中间件用法几乎一样,唯一的区别是——它绑定在 express.Router() 实例上。
说白了,就是给一组路由单独配置中间件。这在大型项目中特别有用。
const express = require('express');
const router = express.Router();
// 路由级中间件 - 验证用户是否登录
router.use((req, res, next) => {
if (!req.headers.authorization) {
return res.status(401).json({ error: '未授权' });
}
next();
});
// 这个路由会被上面的中间件拦截检查
router.get('/profile', (req, res) => {
res.json({ name: '张三', age: 28 });
});
// 这个路由不需要登录,所以放在中间件之前
router.get('/public', (req, res) => {
res.json({ message: '公开信息' });
});
module.exports = router;
我在项目中遇到过这样的场景:后台管理系统有几十个接口,其中大部分需要管理员权限。用路由级中间件,我只需要在 admin 路由上挂一个权限校验中间件,所有子路由自动继承。省事多了。
注意:路由级中间件只对当前 Router 实例下的路由生效。如果你在 app.js 里用 app.use('/admin', adminRouter),那 adminRouter 里的中间件只处理 /admin 开头的请求。
3.4 错误处理中间件
错误处理中间件比较特殊。它有四个参数:(err, req, res, next)。多了一个 err 参数。
Express 会识别参数个数。只要中间件函数有四个参数,它就自动变成错误处理中间件。
// 普通路由
app.get('/user/:id', (req, res, next) => {
const user = findUser(req.params.id);
if (!user) {
// 手动触发错误
const err = new Error('用户不存在');
err.status = 404;
return next(err); // 把错误传给错误处理中间件
}
res.json(user);
});
// 错误处理中间件 - 必须放在所有路由之后
app.use((err, req, res, next) => {
console.error('发生错误:', err.message);
// 设置状态码
const status = err.status || 500;
res.status(status).json({
error: {
message: err.message,
status: status
}
});
});
我曾经犯过一个低级错误——把错误处理中间件放在了路由前面。结果呢?错误根本捕获不到,直接返回了 Express 默认的 HTML 错误页面。嗯,顺序真的很重要。
最佳实践:错误处理中间件一定要放在所有路由和中间件的最后面。它就像一张安全网,兜住所有漏网之鱼。
3.5 内置中间件 - express.static
Express 自带了一些中间件,其中最常用的就是 express.static。它用来托管静态文件。
说白了,就是让用户能直接访问你服务器上的图片、CSS、JS 文件。
const express = require('express');
const path = require('path');
const app = express();
// 托管 public 目录下的静态文件
app.use(express.static('public'));
// 也可以指定虚拟路径前缀
app.use('/static', express.static(path.join(__dirname, 'public')));
// 现在访问 http://localhost:3000/static/logo.png
// 就能拿到 public/logo.png 文件
你想想看,如果没有这个中间件,你得手动写路由来处理每个静态文件的请求。那得写到猴年马月去?
我的建议:生产环境中,最好用 Nginx 来托管静态文件,性能比 Express 好得多。express.static 更适合开发环境或小型项目。
3.6 中间件的执行顺序
这个问题我经常被问到。中间件的执行顺序其实很简单——按照挂载顺序从上到下执行。
但有几个细节要注意:
- 如果中间件不调
next(),后面的中间件就不会执行 - 如果中间件调了
res.send()或res.json(),请求就结束了 - 错误处理中间件只在调用了
next(err)时才会触发
| 中间件类型 | 挂载方式 | 执行时机 |
|---|---|---|
| 应用级 | app.use() | 所有请求 |
| 路由级 | router.use() | 匹配的路由 |
| 错误处理 | app.use(err, req, res, next) | 发生错误时 |
| 内置中间件 | express.static() | 静态文件请求 |
好了,中间件这块就聊到这儿。说白了,中间件就是 Express 的插件机制。理解了这个,你就能随心所欲地扩展你的 API 服务了。下一章咱们聊聊路由的高级用法,到时候你会更深刻地体会到中间件的妙处。