You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Joi验证中间件无法获取req.body请求体问题排查

问题描述

我有一个用于验证user对象的中间件,在用户创建和编辑时生效。不久前它还能正常工作,但今早修改后发现失效了,找不到原因。

排查后确认:中间件函数已被调用(通过console.log测试),但从中间件中获取的req.body为undefined。

Joi Schema

const Joi = require('joi');

module.exports.userSchema = Joi.object({
    user: Joi.object({
        username: Joi.string().min(2).max(40).required(),
        password: Joi.string().min(8).max(15).required(),
        email: Joi.string().email({ minDomainSegments: 2, tlds: { allow: ['com', 'net'] } }).required(),
        location: Joi.string().alphanum(),
        image: Joi.string().alphanum(),
        website: Joi.string().uri({
            scheme: [
                /https?/
            ]
        }),
        stripeId: Joi.string().alphanum(),
    })
});

中间件函数

const { userSchema } = require('./schemas.js');
const ExpressError = require('./utils/ExpressError');

module.exports.validateUser = (req, res, next) => {
    const { error } = userSchema.validate(req.body);
    console.log("validation schema running");   // 输出`validation schema running`
    console.log(req.body)  // 输出`undefined`
    if (error) {
        const msg = error.details.map(el => el.message).join(',')
        throw new ExpressError(msg, 400)
    } else {
        next();
    }
}

路由配置

const { validateUser } = require('../middleware');

router.route('/:id')
    .get( isLoggedIn, isUser, catchAsync(users.showUser))
    .put( validateUser, isLoggedIn, upload.array('image'), catchAsync(users.updateUser))

控制器函数

module.exports.updateUser = async (req, res) => {
    const { id } = req.params;
    console.log("req body ", req.body);
    const user = await User.findByIdAndUpdate(id, { ...req.body.user });
    const imgs = req.files.map(f => ({ url: f.path, filename: f.filename }));
    user.image.push(...imgs);
    await user.save();
    res.redirect(`/users/${user._id}`);
};

控制器函数中能正常获取req.body:

req body  [Object: null prototype] {
  user: [Object: null prototype] {
    username: "Dan's Sands",
    location: '1 Main St, Austin, TX 78703',
    email: 'test@test.com',
    website: 'https://test.com'
  }
}

补充:服务器请求体解析逻辑(App.js)

app.use(bodyParser.raw({type: "application/json"}));
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: true }));

为什么请求体无法传递到我的中间件函数中?


问题原因与解决办法

核心原因:中间件顺序错误 + 解析器冲突

1. 路由中间件顺序搞反了

你的PUT路由里,validateUser放在了upload.array('image')前面。但upload(应该是multer这类文件上传中间件)才是负责解析带文件的表单数据、填充req.body的关键。

因为你当前的请求是multipart/form-data类型(包含文件上传),express.json()和express.urlencoded()都处理不了这种格式,只有multer的upload中间件能解析。所以validateUser先执行时,req.body还没被解析,自然是undefined;而upload之后的控制器就能拿到正确的req.body。

2. App.js里的解析器冗余冲突

你同时用了bodyParser.raw({type: "application/json"})和express.json(),这俩都是解析JSON请求体的中间件,重复设置可能导致解析异常。而且对于multipart请求,这俩完全不起作用。

解决步骤

  1. 调整路由中间件顺序
    把validateUser挪到upload.array('image')后面,确保req.body被multer解析后再做验证:
router.route('/:id')
    .get( isLoggedIn, isUser, catchAsync(users.showUser))
    .put( isLoggedIn, upload.array('image'), validateUser, catchAsync(users.updateUser))
  1. 清理App.js的冗余解析器
    删掉重复的JSON解析中间件,保留必要的即可:
// 移除这行,express.json()已经包含相同功能
// app.use(bodyParser.raw({type: "application/json"}));
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: true }));
  1. 修正Joi Schema的小问题(可选)
    你的Schema里password设为必填,但控制器收到的请求体里没有password字段,验证会失败。如果编辑用户时不需要强制输入密码,可以把password改为可选:
password: Joi.string().min(8).max(15), // 去掉.required()

内容的提问来源于stack exchange,提问作者econobro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 05:40:22