如何在Express.js应用级中间件中获取请求参数以校验MongoDB记录?
问题原因
Express 中,应用级中间件(app.use(checkRecordsExistence))会在路由匹配完成前执行,此时框架还未解析 URL 中的路由参数,所以req.params是空对象。而路由级中间件绑定在具体路由上,会在路由匹配、参数解析完成后执行,因此能正常获取req.params。
解决方案
方法1:在顶层路由统一挂载中间件(推荐)
创建一个根路由,先挂载校验中间件,再将所有子路由挂载到这个根路由下。这样所有请求经过根路由时,已经完成路由匹配和参数解析,中间件可以正常获取req.params,同时无需在每个子路由单独添加中间件,符合 DRY 原则。
示例代码:
const express = require('express'); const app = express(); const asyncHandler = require('express-async-handler'); const checkRecordsExistence = asyncHandler(async (req, res, next) => { const { params } = req; for (const key in params) { if (Object.hasOwnProperty.call(params, key)) { if (key.endsWith('Id')) { const modelNameLower = key.slice(0, -2); const modelName = modelNameLower.charAt(0).toUpperCase() + modelNameLower.slice(1); const Model = mongoose.model(modelName); const recordId = params[key]; const record = await Model.findById(recordId); if (!record) { return next(new ErrorResponse( new Error(message.notFound(modelName)), 404 )); } req[`_${modelNameLower}`] = record; } } } next(); }); // 创建根路由实例 const apiRouter = express.Router(); // 根路由统一挂载校验中间件 apiRouter.use(checkRecordsExistence); // 挂载各业务子路由到根路由 apiRouter.use('/users', require('./routes/users')); apiRouter.use('/posts', require('./routes/posts')); // ...其他业务路由 // 将根路由挂载到应用 app.use('/api', apiRouter);
方法2:优化中间件挂载路径(适配简单路由场景)
如果你的路由直接定义在app上,可通过通配符路径挂载中间件,确保它在路由匹配后执行。比如:
// 匹配所有包含以Id结尾参数的路径 app.use(/\/.*[a-zA-Z]+Id\/?/, checkRecordsExistence); // 定义业务路由 app.get('/users/:userId', (req, res) => { ... }); app.get('/posts/:postId', (req, res) => { ... });
不过这种方式灵活性较差,仅适合路由结构简单的场景。
额外优化建议
- 缓存模型实例:避免每次调用
mongoose.model(modelName),可提前导入所有模型并存储在对象中,提升性能:// 提前导入模型 const models = { User: require('./models/User'), Post: require('./models/Post') // ...其他模型 }; // 中间件中替换为 const Model = models[modelName]; - 添加跳过校验的开关:若某些路由无需校验,可在路由处理函数中设置
req.skipRecordCheck = true,并在中间件开头判断:if (req.skipRecordCheck) return next();
内容的提问来源于stack exchange,提问作者Trung Nguyễn Hoàn
相关产品推荐
相关产品推荐

