mongoose: unique validation on update

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 to query.

So you need to do this:

UserInfo.findOneAndUpdate(
    { userId: userInfo.userId },
    userInfo,
    { runValidators: true, context: 'query' }
)

你可能感兴趣的:(mongoose: unique validation on update)