做服务端开发的小伙伴应该都有过这样的经历:一个项目里,每个接口都要打印请求日志、校验参数合法性、判断用户是否登录,重复的代码写了一遍又一遍,想改规则得找N个地方,改完还容易漏。用Node.js中间件就能完美解决这个问题——把通用的、重复的逻辑抽成可复用的模块,就像流水线的各个工位,请求进来后依次经过这些工位,最后到接口逻辑,既干净又好维护。
一、为什么服务端开发离不开中间件
1.1 中间件的通俗理解
你可以把Node.js中间件比作奶茶店的制作流程:客人点单后,先经过「取单登记」这个环节,再到「制作奶茶」的核心环节,最后「打包递餐」。这个「取单登记」的环节就是中间件,它只做登记这件事,不会干扰核心制作流程。对应到服务端,中间件就是处理请求的中间环节,每个中间件只做一件通用的事,所有请求都会经过这些环节。
1.2 中间件解决的核心痛点
刚接触Node.js的开发者很容易把日志、参数校验这类逻辑直接塞到每个接口里,小项目还行,项目变大后,几十个接口都要改校验规则,得一个个找过去,改完还怕漏,不仅效率低,还容易出bug。中间件刚好解决这个问题:把通用逻辑抽出来,做成可复用的模块,改一次就能让所有用到的接口生效,大大减少重复代码和维护成本。
二、手写第一个可复用的Node.js中间件
我们拿最常用的Express框架来举例,它对中间件的支持非常友好,适合新手练习。
2.1 准备工作
先初始化项目,安装Express框架,执行以下命令:
# 初始化npm项目,生成package.json配置文件
npm init -y
# 安装Express框架
npm install express
2.2 实现请求日志中间件
这个中间件的作用是打印每个请求的访问信息,比如请求时间、方法、路径、响应状态码和处理耗时,所有接口都能复用,不用重复写代码,实现代码如下:
// 技术栈:Node.js + Express(单一技术栈,无混合使用其他工具)
const express = require('express');
const app = express();
// 定义请求日志中间件函数,接收请求对象、响应对象、下一个环节的回调
const requestLogger = (req, res, next) => {
// 记录请求开始时间,用来计算处理耗时
const requestStartTime = Date.now();
// 监听响应完成事件,等接口处理完后打印日志
res.on('finish', () => {
const duration = Date.now() - requestStartTime;
// 打印格式化日志,方便排查问题
console.log(`[${new Date().toLocaleString()}] ${req.method} ${req.path} | 状态码:${res.statusCode} | 耗时:${duration}ms`);
});
// 必须调用next(),否则请求会卡住,不会进入后续逻辑——这是新手最容易踩的坑!
next();
};
// 把中间件注册到Express应用,所有进入该应用的请求都会先经过这个中间件
app.use(requestLogger);
// 写一个测试接口,验证中间件是否生效
app.get('/api/test', (req, res) => {
res.send('Hello,Node.js中间件!');
});
// 启动服务,监听3000端口
app.listen(3000, () => {
console.log('服务已启动,访问地址:http://localhost:3000');
});
把代码保存为app.js,执行node app.js启动服务,访问测试地址后,就能在控制台看到请求日志,说明中间件生效了。如果再写一个用户列表接口,只要在中间件注册后,日志会自动记录,不用再写一遍日志逻辑,这就是可复用的价值。
三、实战:通用参数校验中间件开发
刚才的日志中间件是全局的,所有请求都用;那如果要针对特定接口做参数校验,比如登录接口,总不能每个接口都写校验逻辑吧?我们可以做一个通用的参数校验中间件,只要传入校验规则,就能给任意接口复用。
3.1 场景分析
登录、注册、修改资料这类接口,都需要校验参数:手机号必须是11位有效格式,密码至少6位。如果把校验逻辑写在每个接口里,以后改规则要改N个地方;做成中间件后,改一次就生效,还能复用。
3.2 技术选型
还是用Node.js+Express,再加上参数校验库Joi,简化校验逻辑,安装命令如下:
# 安装Joi参数校验库
npm install joi
3.3 实现通用参数校验中间件
代码如下:
// 技术栈:Node.js + Express + Joi
const express = require('express');
const Joi = require('joi');
const app = express();
// 通用参数校验中间件,接收Joi定义的校验规则作为参数
const validateParams = (schema) => (req, res, next) => {
// 校验请求体里的参数,如果是GET请求则校验query参数
const validateResult = schema.validate(req.body);
if (validateResult.error) {
// 校验失败,返回清晰的错误信息给前端
return res.status(400).json({
code: 400,
msg: `参数错误:${validateResult.error.details[0].message}`
});
}
// 校验通过,进入后续业务逻辑
next();
};
// 定义登录接口的校验规则:手机号必须是11位有效格式,密码至少6位
const loginSchema = Joi.object({
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required().messages({
'string.pattern.base': '手机号格式不正确,请输入11位中国大陆手机号',
'any.required': '手机号不能为空'
}),
password: Joi.string().min(6).required().messages({
'string.min': '密码至少需要6位',
'any.required': '密码不能为空'
})
});
// 注册登录接口,把校验中间件加到路由上,只有通过校验才能进入业务逻辑
app.post('/api/login', validateParams(loginSchema), (req, res) => {
// 这里只写核心的登录业务逻辑,不用再处理参数校验
res.json({ code: 200, msg: '登录成功' });
});
// 启动服务
app.listen(3000);
这个中间件的优势很明显:以后写注册接口,只要定义注册的校验规则,再把中间件传入新规则,就能直接复用,不用再写重复的校验代码。
四、中间件的核心要点梳理
4.1 主要应用场景
中间件的应用非常广泛,常见场景包括:
- 请求日志:记录所有请求的情况,方便排查问题;
- 参数校验:确保接口参数合法,避免业务逻辑出错;
- 权限校验:判断用户是否登录,解析token获取用户信息,不用每个接口都解析;
- 错误处理:统一返回错误格式,不用每个接口都写try-catch;
- 跨域处理:允许指定域名访问接口,不用每个接口设置响应头。
4.2 技术优缺点分析
优点:
- 复用性极强:一个中间件可用于多个项目、多个接口,减少重复代码;
- 维护方便:修改中间件逻辑,所有用到的地方自动更新,不用逐个修改;
- 分层清晰:通用逻辑和业务逻辑分离,业务开发者只关心核心功能;
- 安全性统一:权限、参数校验这类规则统一在中间件,避免漏加导致安全问题。 缺点:
- 顺序敏感:中间件顺序错了会出问题,比如日志要放权限校验前面,否则拦截的请求不会被记录;
- 过多中间件增加耗时:逻辑复杂的中间件会延长请求处理时间;
- 新手易踩坑:忘写next()会导致请求卡住,顺序错了功能失效。
4.3 开发注意事项
- 必须调用next():这是核心规则,中间件不调用next()请求会一直挂起;
- 中间件顺序:全局中间件(日志、跨域)放最前面,路由级中间件(权限校验)放对应路由前面,错误处理中间件放最后;
- 单一职责:一个中间件只做一件事,比如不要把日志和权限校验放一起,方便复用;
- 可配置性:尽量把中间件做成可配置的,比如日志中间件可以配置是否脱敏密码;
- 异常处理:要考虑参数不存在、规则出错等异常情况,避免服务端崩溃。
五、实战总结与扩展思考
通过两个实战示例,我们可以看到Node.js中间件的核心价值:把通用、重复的逻辑抽成可复用的模块,让服务端开发更高效、代码更干净。写中间件的思路很简单:先找到项目里重复的代码段,封装成可配置的函数,最后在需要的地方注册使用。 如果要扩展,我们可以把写好的中间件做成npm包分享给其他项目,或者根据环境变量配置中间件的开关(比如开发环境打印详细日志,生产环境简化日志)。掌握中间件开发,能帮你打造更优雅、易维护的Node.js服务端项目,大幅提升开发效率。
评论
围绕“Node.js中间件开发实战:构建可复用的JavaScript服务端功能模块”参与讨论