Mongoose+Express中Patch单字段更新失败,需区分创建更新校验
问题:Mongoose更新用户时触发必填字段校验,无法单字段修改
我用Node.js、Express和Mongoose开发API,User模型里把name、email、password设为必填字段。但编写Patch接口尝试单字段更新时,会触发必填校验报错,要求提供所有必填字段的值。我希望创建用户时校验必填字段,更新时允许单独修改单个字段,求解决办法。
我的User模型
const mongoose = require('mongoose') const validator = require('validator') // 创建用户Schema const userSchema = new mongoose.Schema({ name: { type: String, required: true, trim: true, }, age: { type: Number, default: 0, }, email: { type: String, required: true, unique: true, trim: true, validate(value) { if (!validator.isEmail(value)) { throw new Error('invalid email') } }, }, password: { type: String, required: true, trim: true, minLength: 7, validate(value) { if (value.includes('password')) { throw new Error('password cannot be password. I mean come on!') } }, }, }) // 保存前的中间件 userSchema.pre('save', async function (next) { const user = this console.log('from the middle ware you are about to save the user') next() }) const User = mongoose.model('User', userSchema) module.exports = User
Patch接口代码
router.patch('/users/:id', async (req, res) => { // 限制用户只能更新允许的字段 const userSentUpdateKeys = Object.keys(req.body) const allowedUpdateFields = ['name', 'age', 'email', 'password'] const isValidUpdate = userSentUpdateKeys.every((update) => allowedUpdateFields.includes(update) ) if (!isValidUpdate) { return res.status(400).send({ error: 'invalid update' }) } try { // 注释掉的findByIdAndUpdate写法 // const user = await User.findByIdAndUpdate(req.params.id, req.body, { // runValidators: true, // new: true, // }) const user = await User.findById(req.params.id) allowedUpdateFields.forEach((update) => (user[update] = req.body[update])) await user.save() !user ? res.status(404).send() : res.status(201).send(user) } catch (e) { res.status(400).send(e) } })
报错响应(400)
{ "errors": { "password": { "name": "ValidatorError", "message": "Path `password` is required.", "properties": { "message": "Path `password` is required.", "type": "required", "path": "password" }, "kind": "required", "path": "password" }, "email": { "name": "ValidatorError", "message": "Path `email` is required.", "properties": { "message": "Path `email` is required.", "type": "required", "path": "email" }, "kind": "required", "path": "email" } }, "_message": "User validation failed", "name": "ValidationError", "message": "User validation failed: password: Path `password` is required., email: Path `email` is required." }
解决方案
方法1:给required设置函数,区分创建与更新场景
Mongoose的required支持传入函数,可通过判断文档是否为新创建(this.isNew)来决定是否触发必填校验。
修改User模型中的必填字段配置:
const userSchema = new mongoose.Schema({ name: { type: String, required: function() { // 仅创建新文档时校验必填 return this.isNew }, trim: true, }, email: { type: String, required: function() { return this.isNew }, unique: true, trim: true, validate(value) { if (!validator.isEmail(value)) { throw new Error('invalid email') } }, }, password: { type: String, required: function() { return this.isNew }, trim: true, minLength: 7, validate(value) { if (value.includes('password')) { throw new Error('password cannot be password. I mean come on!') } }, }, // 其他字段保持不变 })
此方式下,创建用户时会强制校验必填项,更新时仅校验被修改字段的规则(比如修改邮箱时会触发格式校验,不修改则不会触发)。
方法2:仅赋值用户实际传入的字段,避免覆盖为undefined
当前代码中用allowedUpdateFields.forEach遍历所有允许字段,会把未传入的字段赋值为undefined,触发必填校验。改为只遍历用户实际提交的字段:
修改Patch接口中的赋值逻辑:
// 替换原有的forEach代码 userSentUpdateKeys.forEach((update) => { user[update] = req.body[update] })
这样只有用户传入的字段会被修改,其他字段保持原有值,不会触发必填校验。
方法3:使用findByIdAndUpdate并配置正确校验规则
如果想用findByIdAndUpdate,需注意:默认情况下它不会执行Schema校验,也不会触发pre('save')中间件。要开启校验需添加runValidators: true,若需触发中间件,可配合context: 'query'配置(中间件逻辑需调整)。
示例:
const user = await User.findByIdAndUpdate(req.params.id, req.body, { runValidators: true, new: true, context: 'query' // 可选,让pre('save')中间件在更新时触发(需调整中间件逻辑) })
注意:使用此方法仍需结合方法1,区分创建与更新场景,否则必填校验仍会触发。
内容的提问来源于stack exchange,提问作者Raghav malhotra
相关产品推荐
相关产品推荐

