3、第三方中间件:body-parser解析请求体、cookie-parser处理Cookie、morgan日志记录、cors跨域处理
说实话,刚入行那会儿,我总觉得第三方中间件就是「装个包、use一下」的事。直到有一次线上接口莫名其妙报 404,排查半天才发现是 body-parser 没配好,请求体根本没解析出来。嗯,从那以后我再也不敢小看这几个「小工具」了。
今天咱们就聊聊 Express 生态里最常用的四个第三方中间件。它们各自解决什么问题?怎么用?有哪些坑?我一个一个说。
3.1 body-parser:请求体的「翻译官」
HTTP 请求体说白了就是一段原始字节流。你想想看,服务器收到的 POST 数据其实是这样的:
username=admin&password=123456
或者这样的:
{ "username": "admin", "password": "123456" }
但 Node.js 的 req 对象不会自动帮你解析。你得手动拼 chunk、转字符串、再 JSON.parse。我早期写的一个项目就是这么干的,代码又臭又长。
body-parser 就是来干这个的。它把请求体解析成你想要的格式,然后挂到 req.body 上。
核心用法:
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
// 解析 JSON 格式的请求体
app.use(bodyParser.json());
// 解析 URL-encoded 格式(表单提交)
app.use(bodyParser.urlencoded({ extended: true }));
app.post('/login', (req, res) => {
console.log(req.body); // 直接拿到解析后的对象
res.json({ status: 'ok' });
});
这里有个细节:extended: true 表示使用 qs 库来解析,支持嵌套对象。如果设成 false,就用 querystring 库,不支持嵌套。我个人习惯用 true,因为前端传复杂结构时更灵活。
小技巧:如果你用的是 Express 4.16+,其实内置了 express.json() 和 express.urlencoded(),不需要额外装 body-parser 包。但为了兼容老项目,我还是习惯显式引入。
3.2 cookie-parser:Cookie 的「拆箱工」
Cookie 这玩意儿,本质上就是一段字符串。浏览器发请求时,会在 Cookie 头里塞一串东西:
Cookie: name=zhangsan; session_id=abc123; theme=dark
你手动解析也不是不行,但每次都要写 split(';') 再 trim(),烦不烦?
cookie-parser 帮你一步到位。
const cookieParser = require('cookie-parser');
app.use(cookieParser());
app.get('/', (req, res) => {
console.log(req.cookies); // { name: 'zhangsan', session_id: 'abc123', theme: 'dark' }
});
我在项目中遇到过一个问题:签名 Cookie。有些敏感信息(比如用户身份)不能明文存,得加个签名防篡改。cookie-parser 支持传一个 secret 参数:
app.use(cookieParser('my_secret_key'));
// 设置签名 Cookie
res.cookie('token', 'xxxx', { signed: true });
// 读取签名 Cookie
console.log(req.signedCookies); // 只有签名验证通过才能读到
注意:签名不是加密!它只是加了个 HMAC 校验。如果真需要隐藏 Cookie 内容,得用加密方案。我曾经见过有人把密码明文放 Cookie 里还加了签名,以为安全了……嗯,这是两码事。
3.3 morgan:请求日志的「记录员」
你想想看,线上服务跑着跑着出问题了,你第一件事是什么?查日志。没有日志,你连谁在什么时候调了什么接口都不知道。
morgan 就是 Express 生态里最流行的日志中间件。它帮你自动记录每个 HTTP 请求的信息。
const morgan = require('morgan');
app.use(morgan('dev'));
就这么一行,控制台就会输出类似:
POST /login 200 12.345 ms - 42
GET /users 304 - -
morgan 支持多种预定义格式:
| 格式 | 说明 | 输出示例 |
|---|---|---|
dev |
开发模式,彩色输出 | GET / 200 3.123 ms - 12 |
combined |
Apache 标准格式 | ::1 - - [10/Oct/2023:12:00:00] "GET /" 200 12 |
short |
精简格式 | ::1 - GET / HTTP/1.1 200 12 - 3.123 ms |
tiny |
最简格式 | GET / 200 12 - 3.123 ms |
我个人习惯:开发环境用 dev,生产环境用 combined 配合日志轮转工具。为什么呢?因为 combined 格式包含客户端 IP、时间戳、User-Agent,排查问题时信息更全。
进阶用法:你还可以自定义日志格式,甚至把日志写入文件:
const fs = require('fs');
const path = require('path');
const accessLogStream = fs.createWriteStream(
path.join(__dirname, 'access.log'), { flags: 'a' }
);
app.use(morgan('combined', { stream: accessLogStream }));
3.4 cors:跨域的「通行证」
跨域问题,说白了就是浏览器的「同源策略」在作怪。你前端跑在 localhost:3000,后端跑在 localhost:4000,浏览器就会报错:
Access to XMLHttpRequest at 'http://localhost:4000/api' from origin 'http://localhost:3000' has been blocked by CORS policy.
我刚开始做前后端分离时,被这个错误折磨了好几天。后来才知道,加个 cors 中间件就解决了。
const cors = require('cors');
app.use(cors()); // 允许所有来源
但生产环境可不能这么干。你想想看,如果允许所有来源,那别人随便一个网站都能调你的接口,安全吗?
正确的做法是配置白名单:
const corsOptions = {
origin: ['https://myapp.com', 'https://admin.myapp.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
credentials: true // 允许携带 Cookie
};
app.use(cors(corsOptions));
我曾经踩过的坑:配置了 credentials: true 之后,origin 不能设为 '*',否则浏览器会拒绝请求。还有,如果前端用了自定义请求头(比如 X-Requested-With),记得在 allowedHeaders 里加上,不然会触发预检请求失败。
说到预检请求(OPTIONS),很多新手会忽略。浏览器在发送复杂请求(比如带自定义头的 POST)之前,会先发一个 OPTIONS 请求「试探」一下。cors 中间件会自动处理这个,你不需要额外操心。
3.5 组合使用:一个完整的例子
好了,四个中间件都讲完了。咱们把它们组合起来,看看一个典型的 Express 应用长什么样:
const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const morgan = require('morgan');
const cors = require('cors');
const app = express();
// 1. 日志记录(放最前面)
app.use(morgan('dev'));
// 2. 跨域处理
app.use(cors({
origin: 'http://localhost:3000',
credentials: true
}));
// 3. 解析请求体
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// 4. 解析 Cookie
app.use(cookieParser('my_secret'));
// 路由
app.post('/api/login', (req, res) => {
const { username, password } = req.body;
// 登录逻辑...
res.cookie('session', 'abc123', { signed: true, httpOnly: true });
res.json({ success: true });
});
app.listen(4000, () => {
console.log('Server running on port 4000');
});
中间件的顺序很重要!比如 morgan 要放最前面,这样能记录所有请求,包括那些被后续中间件拦截的。cookie-parser 要在路由之前,不然路由里拿不到 req.cookies。
嗯,这四个中间件,说白了就是 Express 开发的「四大护法」。你掌握了它们,大部分 Web 应用的基础设施就搭起来了。下一章咱们聊聊更高级的话题——错误处理中间件和自定义中间件。到时候你会发现,理解了这些基础,写自己的中间件其实也不难。