Search code examples
mongoosemiddlewaremongoose-schema

Mongoose: MongoError won't prevent pre hooks to run?


I'm using Mocha to make some tests in my API and I noticed that when I have a field with unique: true and I make the tests on a duplicate field, all of my pre('save') still are called. Am I doing something wrong?

user.js

const UserSchema = new Schema({
  email: {
    type: String,
    unique: true
  }
});

UserSchema.pre('save', function test(next) {
  console.log(123);
});

test.js

var user1 = await User.findOne({ email: "[email protected]" });
var user2 = new User({ email: "[email protected]" });
await user2.save()

console:

123
MongoError: E11000 duplicate key error collection (...)

Here there is an image of my test. In the "creates a new document when valid" I create two new users. In the "email is unique ..." test, I try to create another with the same email as I created before. The "1234567" are console.log I put inside my pre hook.


Solution

  • After a lot of research I discovered how things work with unique and pre hooks.

    This is the best way to fix this kind of problem.

    Edit: some details for this answer as Tyler appointed that it could become invalid.

    In my case, my pre saves have crucial logic with relationships. So when I was trying to save an invalid document, all the relationships of it were being associated with a wrong document.

    Since async pre hooks are called all together, I had to garantee that my unique validation was called before everything. Using .path('field').validate was the best way I found so far to do it, since it is called even before .pre('validate'). Here is my code:

    UserSchema.path('email').validate(async function validateDuplicatedEmail(value) {
        if (!this.isNew && !this.isModified('email')) return true;
    
        try {
            const User = mongoose.model("User");
    
            const count = await User.countDocuments({ email: value });
            if (count > 0) return false;
    
            return true;
        }
        catch (error) {
            return false;
        }
    }, "Email already exists");