Here is the userSchema
:
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
trim: true,
lowercase: true,
},
password: {
type: String,
required: true,
trim: true,
},
});
userSchema.pre("save", async function (next) {
const user = this;
console.log("before saving");
next();
});
const User = mongoose.model("User", userSchema);
module.exports = { User };
and following is the code to update the user:
router.put("/users/:id", async (req, res) => {
// const user = new User(req.body);
try {
console.log(req.body);
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
if (!user) {
res.status(404).send();
return;
}
res.status(200).send(user);
} catch (error) {
res.status(400).send(error);
}
});
Since both username and password fields are required, I assumed that when I try to update the user with empty req.body
, mongoose would through an error, But it isn’t.
What is the issue here?
2
Answers
The behavior you’re observing is expected in Mongoose when using the findByIdAndUpdate method with the runValidators: true option. By default, Mongoose does not enforce required fields when updating documents using this method. The reason for this behavior is that Mongoose assumes you are updating an existing document, and therefore, it doesn’t treat missing fields as validation errors.
If you want to enforce that the username and password fields are required when updating a user, you can add explicit validation checks in your route handler before calling findByIdAndUpdate. For example, you can check if req.body contains the required fields and is not empty before proceeding with the update. Here’s an updated version of your code that adds this validation:
So firstly you should never send
req.body
as the parameter tofindByIdAndUpdate
. I can’t think of a good reason to sendreq.body
as the parameter to anyModel.find*
method but let me illustrate why you definitely shouldn’t send it in this case.If your
userSchema
contains fields that should not be mutated by unauthorised users then they can be compromised in thereq.body
payload. Let’s say your schema has a field namedadmin
that determines if user has elevated privileges. This example works for almost any field but admin privileges are a simple concept to grasp. Your schema might look like this:Now when you pass
req.body
a user could send akey:value
pair in the body of the put/post request withadmin: true
. It would look like this:Now this user has
admin:true
stored in the document.Instead you should destructure the
req.body
and only pass the properties that you expect (after you’ve sanitised them of course but that’s a different post). This is how it would look:To answer your actual question. You are not alone in your assumptions. This catches nearly everyone out who uses Mongoose. The docs state:
To illustrate in your example a document might look like this:
Your update might look like this:
Your document now looks like:
Now despite your
password
being a required field the update works becauseusername
is the only field being updated and it met the validation (type: String, lowercase: true
). Imagine having a document with hundreds of properties, nested objects inside arrays of objects etc. That is normal in MongoDB. If Mongoose did not implement this behaviour you would otherwise have to pass the whole document only to update one field.To summarise, by passing
req.body
to thefindByIdAndUpdate
you passed an empty object. Mongoose therefore had nothing to update and nothing to validate. TherunValidators
only works on fields included in the update object.