Express中isAuthor中间件报错改造:实现跨模型复用
Express通用作者校验中间件改造方案
问题背景
需要实现一个通用中间件,检查当前登录用户是否为内容发布者,仅允许其编辑自己的帖子,同时要让中间件独立于模型实现复用。当前实现的中间件和路由在使用时报错。
当前中间件代码
const asyncHandler = require("express-async-handler"); const { carSchema } = require("./schemas.js"); const Car = require("./models/car"); module.exports.isAuthor = asyncHandler(async (req, res, next, Model) => { const { id } = req.params; const document = await Model.findById(id); if (!document.author.equals(req.user._id)) { req.flash("error", "Not allowed"); return res.redirect(`/${Model.collection.name}/${id}`); } next(); });
当前路由代码
const express = require("express"); const router = express.Router(); const asyncHandler = require("express-async-handler"); const Car = require("../models/car"); const { isLoggedIn, isAuthor, validateCar } = require("../middleware"); router.get( "/:id/edit", isLoggedIn, isAuthor.bind(null, Car), asyncHandler(async (req, res) => { const car = await Car.findById(req.params.id); if (!car) { req.flash("error", "This ad doesnt exist"); return res.redirect("/cars"); } res.render("cars/editCar", { car }); }) );
报错信息
Cannot destructure property 'id' of 'req.params' as it is undefined. TypeError: Cannot destructure property 'id' of 'req.params' as it is undefined. at middleware.js:26:11 at asyncUtilWrap (node_modules\express-async-handler\index.js:3:20) at Layer.handle [as handle_request] (node_modules\express\lib\router\layer.js:95:5) at next (node_modules\express\lib\router\route.js:144:13) at module.exports.isLoggedIn (middleware.js:11:3) at Layer.handle [as handle_request] (node_modules\express\lib\router\layer.js:95:5) at next (node_modules\express\lib\router\route.js:144:13) at Route.dispatch (node_modules\express\lib\router\route.js:114:3) at Layer.handle [as handle_request] (node_modules\express\lib\router\layer.js:95:5) at node_modules\express\lib\router\index.js:284:15
错误原因
核心问题是中间件的参数传递方式错误。Express中间件的固定参数顺序是(req, res, next),但用isAuthor.bind(null, Car)时,会把Car作为第一个参数传入中间件函数,导致原函数里的req被替换成了Car,真正的req变成了第二个参数,自然无法读取req.params。
改造方案
把isAuthor改成高阶函数,让它接收Model作为参数,然后返回一个符合Express规范的中间件函数。这样既可以复用,又能正确获取请求对象。
改造后的中间件代码
const asyncHandler = require("express-async-handler"); module.exports.isAuthor = (Model, options = {}) => { // 允许自定义id参数名,默认取req.params.id const idField = options.idField || 'id'; return asyncHandler(async (req, res, next) => { const docId = req.params[idField]; if (!docId) { req.flash("error", "缺少文档ID"); return res.redirect("back"); } const document = await Model.findById(docId); if (!document) { req.flash("error", "该内容不存在"); return res.redirect(`/${Model.collection.name}`); } // 校验用户是否为作者 if (!document.author.equals(req.user._id)) { req.flash("error", "无权限编辑该内容"); return res.redirect(`/${Model.collection.name}/${docId}`); } // 可选:把文档挂载到req上,后续路由无需再查询 req.doc = document; next(); }); };
改造后的路由使用方式
不需要再用bind,直接调用isAuthor(Car)返回中间件即可:
const express = require("express"); const router = express.Router(); const asyncHandler = require("express-async-handler"); const Car = require("../models/car"); const { isLoggedIn, isAuthor, validateCar } = require("../middleware"); router.get( "/:id/edit", isLoggedIn, isAuthor(Car), // 直接传入模型 asyncHandler(async (req, res) => { // 可以直接用req.doc,省去重复查询 const car = req.doc; res.render("cars/editCar", { car }); }) );
额外优化点
- 支持自定义ID字段:如果路由里用的不是
:id而是:postId,可以通过isAuthor(Post, { idField: 'postId' })来适配。 - 处理文档不存在的情况:原代码没判断
document是否为null,改造后增加了该逻辑,避免后续报错。 - 挂载文档到req对象:减少重复数据库查询,提升性能。
- 错误提示更清晰:不同场景返回不同的错误信息,方便用户排查。
内容的提问来源于stack exchange,提问作者SPQR
相关产品推荐
相关产品推荐

