'mongoose middleware pre update

I am using

schema.pre('save', function (next) {
  if (this.isModified('field')) {
    //do something
  }
});

but I now need to use this same function isModified in a schema.pre('update' hook, but it does not exists. Does anyone know how I can use this same functionality in the update hook?



Solution 1:[1]

Not possible according to this:

Query middleware differs from document middleware in a subtle but important way: in document middleware, this refers to the document being updated. In query middleware, mongoose doesn't necessarily have a reference to the document being updated, so this refers to the query object rather than the document being updated.

update is query middleware and this refers to a query object which has no isModified method.

Solution 2:[2]

@Jeremy I've arrived to the same issue and finally got a workaround:

schema.pre('update', function(next) {
        const modifiedField = this.getUpdate().$set.field;
        if (!modifiedField) {
            return next();
        }
        try {
            const newFiedValue = // do whatever...
            this.getUpdate().$set.field = newFieldValue;
            next();
        } catch (error) {
            return next(error);
        }
    });

Taken from here: https://github.com/Automattic/mongoose/issues/4575

With this, you can check if it comes any update on a field but you can't check if the incoming value is different than stored. It works perfect for my use case (encrypt password after reset)

I hope it helps.

Solution 3:[3]

Schema.pre('updateOne', function (next) {
    const data = this.getUpdate()

    data.password = 'Teste Middleware'
    this.update({}, data).exec()
    next()
})

  const user = await User.updateOne({ _id: req.params.id }, req.body)

this worked to me

Solution 4:[4]

Not quite a solution for OP, but this is what worked for me Best solution I tried, taken from here

schema.pre("update", function(next) {
        const password = this.getUpdate().$set.password;
        if (!password) {
            return next();
        }
        try {
            const salt = Bcrypt.genSaltSync();
            const hash = Bcrypt.hashSync(password, salt);
            this.getUpdate().$set.password = hash;
            next();
        } catch (error) {
            return next(error);
        }
    });

Solution 5:[5]

Actually André Rodrigues Answer was almost perfect, but in Mongoose v5.13.0 you can easily mutate the body itself without executing it by

schema.pre('updateOne', async function () {
  let data = this.getUpdate();
  const salt = await bcrypt.genSalt();
  data.password = await bcrypt.hash(data.password, salt);
});

Welcome as always :)

Solution 6:[6]

Well, what i have done in case of password hashing in update method is:

userRouter.patch("/api/users/:id", async (req, res) => {
  const updatedAttributes = Object.keys(req.body);
  const availableUpates = ["firstName", "lastName", "email", "password"];

  const check = updatedAttributes.every((udate) =>
    availableUpates.includes(udate)
  );

  if (!check) {
    return res.status(400).send("Requested Fields Can't Be Updated");
  }

  const password = req.body.password;
  if (password) {
    const hashedPass = await bcrypt.hash(password, 8);
    req.body.password = hashedPass;
  }

  try {
    const user = await User.findByIdAndUpdate(req.params.id, req.body, {
      runValidators: true,
      new: true,
    });
    res.status(202).send(user);
  } catch (error) {
    res.status(400).send(error);
  }
});

And don't forget to import needed npm modules.

Solution 7:[7]

//Wouldn't work for $inc updates etc...

Schema.pre("updateOne", function(next){
  const data = this.getUpdate()
  let updateKeys = Object.keys(data)
  let queryArr = []
  const checkQuery = (arr, innerData = data) => {
    arr.map((elem) => {
     if (elem === "$set" || elem === "$push") {
       checkQuery(Object.keys(innerData[elem]), innerData[elem]);
     } else {
      queryArr.push(elem);
     }
   })
 };
  checkQuery(updateKeys);
  const isModified = (value) => queryArr.includes(value)
  if(isModified("field")) {
    data.field = whatever...
  }
  
  next()
})

I recursively checked for keys here

Solution 8:[8]

This was my solution, using bcrypt and an async function. One of the big points of confusion for me is that changes are passed under this._update which differs from handling on .pre('save') where they're passed directly. So you see I had to call this._update.password as opposed to simply this.password.

UserSchema.pre('findOneAndUpdate', async function() {
  const userToUpdate = await this.model.findOne(this.getQuery())

  if (userToUpdate.password !== this._update.password) {
    this._update.password = await bcrypt.hash(this._update.password, 12)
  }
})

Solution 9:[9]

This worked for me to change the user password

userSchema.pre("updateOne", function (next) {
  const user = this;
  if (user.getUpdate().password !== undefined) {
    bcrypt.hash(user.getUpdate().password, 10, (err, hash) => {
      if (err) return next(err);
      user.getUpdate().password = hash;
      return next();
    });
  } else {
    return next();
  }
});

Solution 10:[10]

But you can use query hooks; although you might need to use POST, rather than PRE hooks.

schema.pre('findOneAndUpdate', async function() {
  const docToUpdate = await this.model.findOne(this.getQuery());
  console.log(docToUpdate); // The document that `findOneAndUpdate()` will modify
});

So,

schema.post(/update/i, async (query, next) {
  if (query instanceof mongoose.Query) {
    await Model.find(query.getQuery()).each((el) => {
      if (isModified('field')) {
        //do something
      }
    })
  }
  next()
});