Issue
By default the validator logic won't execute when you update a document using mongoose update APIs (e.g. findOneAndUpdate
).
As suggested in this answer, you can use the following code as a workaround:
UserInfo.findOne({ userId: userInfo.userId })
.then((ui) => {
ui.set(userInfo);
return ui.save();
})
This worked perfectly until I introduced mongoose-unique-validator.
mongoose-unique-validator
The mongoose-unique-validator
package is necessary because without it, if you violates unique
constraint, you will get a vague E11000 error from mongoDB.
{
code: 11000
driver: true
errmsg: "E11000 duplicate key error collection: archiprod.userinfos index: phone_1 dup key: { : "01234567891" }"
index: 0
name: "MongoError"
}
It has at least two issues when you want to tell the user which field violates the unique
constraint.
- it's not well structured, so you have to extract the info from the
errmsg
field manually. - It only returns on the first violation. So if you have two or more violations at the same time, you can't show them all to the user.
It's no longer an issue with mongoose-unique-validator
, because you can get the following structured error message:
{
"_message": "UserInfo validation failed",
"errors": {
"email": {
"kind": "unique",
"message": "Error, expected `email` to be unique. Value: `[email protected]`",
"name": "ValidatorError",
"path": "email",
"properties": {
"message": "Error, expected `email` to be unique. Value: `[email protected]`",
"path": "email",
"type": "unique",
"value": "[email protected]"
},
"value": "[email protected]"
},
"phone": {
"kind": "unique",
"message": "Error, expected `phone` to be unique. Value: `01234567891`",
"name": "ValidatorError",
"path": "phone",
"properties": {
"message": "Error, expected `phone` to be unique. Value: `01234567891`",
"path": "phone",
"type": "unique",
"value": "01234567891"
},
"value": "01234567891"
}
},
"message": "UserInfo validation failed: email: Error, expected `email` to be unique. Value: `[email protected]`, phone: Error, expected `phone` to be unique. Value: `01234567891`",
"name": "ValidationError"
}
Validation on Update
However, the introduction of mongoose-unique-validator
package broke the code at the beginning of this post -- it failed with errors for each unique
fields.
I then found the mongoose version 5.7.7 resolved this issue!
You can now enable validation simply by adding { runValidators: true }
option as the 3rd argument.
UserInfo.findOneAndUpdate(
{ userId: userInfo.userId },
userInfo,
{ runValidators: true }
)
Tweak for mongoose-unique-validator
However, there is another trap.
If your document contains a non-_id
field which is an ObjectId
, you'll see the following error
Validation failed: userId: Cannot read property 'ownerDocument' of null
Fortunately this is already mentioned in the README of mongoose-unique-validator
.
For technical reasons, this plugin requires that you also set the
context
option toquery
.
So you need to do this:
UserInfo.findOneAndUpdate(
{ userId: userInfo.userId },
userInfo,
{ runValidators: true, context: 'query' }
)