77

I have this code

var ClientSchema = new Schema({
  name: {type: String, required: true, trim: true}
});

var Client = mongoose.model('Client', ClientSchema);

Using express, I create a new client with this code

var client = new Client(req.body);
client.save(function(err, data) {
  ....
});

If I leave the name field empty on the form, mongoose doesn't allow to create the client because I set it as required on the schema. Also, if I leave spaces before and after the name, mongoose delete that spaces before save.

Now, I try to update a client with this code

var id = req.params.id;
var client = req.body;
Client.update({_id: id}, client, function(err) {
  ....
});

It let me to change the name, but if I leave it empty on the form, mongoose doesn't validate and save an empty name. If I add empty spaces before and after the name, it save the name with spaces.

Why mongoose validate on save but not on update? I'm doing it in the wrong way?

mongodb: 2.4.0 mongoose: 3.6.0 express: 3.1.0 node: 0.10.1

Camilo
  • 2,844
  • 2
  • 29
  • 44
  • 1
    which approach you went ahead with? Just curious, facing exact same issue. Can you please share example in here. Thanks a lot. – Imran Ahmad May 21 '17 at 14:24

7 Answers7

112

As of Mongoose 4.0 you can run validators on update() and findOneAndUpdate() using the new flag runValidators: true.

Mongoose 4.0 introduces an option to run validators on update() and findOneAndUpdate() calls. Turning this option on will run validators for all fields that your update() call tries to $set or $unset.

For example, given OP's Schema:

const ClientSchema = new Schema({
  name: {type: String, required: true, trim: true}
});

const Client = mongoose.model('Client', ClientSchema);

Passing the flag on each update

You can use the new flag like this:

const id = req.params.id;
const client = req.body;
Client.update({_id: id}, client, { runValidators: true }, function(err) {
  ....
});

Using the flag on a pre hook

If you don't want to set the flag every time you update something, you can set a pre hook for findOneAndUpdate():

// Pre hook for `findOneAndUpdate`
ClientSchema.pre('findOneAndUpdate', function(next) {
  this.options.runValidators = true;
  next();
});

Then you can update() using the validators without passing the runValidators flag every time.

David Villamizar
  • 802
  • 9
  • 16
victorkt
  • 13,992
  • 9
  • 52
  • 51
  • 1
    I liked this answer, but the prehook for findOneAndUpdate throws a mongoose error... looks like it might be due to that method not being supported? Also, just specifying the "update" prehook does not fire the validations.. The error: /.../mongoose/node_modules/hooks/hooks.js:149 if ('undefined' === typeof proto[methodName].numAsyncPres) { – Doug Jun 11 '15 at 15:31
  • Doug, the query middleware for adding hooks to findONeAndUpdate was added in Mongoose 4.0 (https://github.com/Automattic/mongoose/issues/2138). Are you sure you are using Mongoose 4.0 or above? – aaronroberson Jun 18 '15 at 22:47
  • 2
    Specifying `{ runValidators: true }` on the update calls works great. But **pre hook option set does not work** for me in Mongoose version 4.1.0 – steampowered Jul 29 '15 at 15:40
77

You're not doing anything wrong, validation is implemented as internal middleware within Mongoose and middleware doesn't get executed during an update as that's basically a pass-through to the native driver.

If you want your client update validated you'll need to find the object to update, apply the new property values to it (see underscore's extend method), and then call save on it.

Mongoose 4.0 Update

As noted in the comments and victorkohl's answer, Mongoose now support the validation of the fields of $set and $unset operators when you include the runValidators: true option in the update call.

asedsami
  • 609
  • 7
  • 26
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • 3
    There is some GitHub tickets about Mongoose validation on update, see [Issue 860](https://github.com/LearnBoost/mongoose/issues/860), [Issue 892](https://github.com/LearnBoost/mongoose/issues/892) and also [Issue 4722](https://github.com/LearnBoost/mongoose/issues/4722). I hope they will fix this soon.. – Yves M. Jan 02 '14 at 16:48
  • _.extend really helped me out here as I needed to update a number of different subdocument fields from a partial json object. Thankyou! – Tom Makin Jan 31 '14 at 09:20
  • 2
    From v3.9.3, update() takes 2 additional options: setDefaultsOnInsert and runValidators @see https://github.com/LearnBoost/mongoose/commit/1d8c3e96c7b11497d3325e9cf1f7ae66c9ee560e –  Oct 13 '14 at 07:38
  • I usually get the document, then use Lo-Dash's merge method to do a deep extend in case I have nested properties....https://lodash.com/docs#merge – Greg Nov 05 '14 at 08:31
16

MongoDB does not run validation on updates by default.

in order to make validation works by default when an update happens, just before connecting to MongoDB you can set global setting only ones like that:

mongoose.set('runValidators', true); // here is your global setting

mongoose.connect(config.database, { useNewUrlParser: true });
mongoose.connection.once('open', () => {
    console.log('Connection has been made, start making fireworks...');
}).on('error', function (error) {
    console.log('Connection error:', error);
});

So any built-in or custom validation will run on any update as well

Muho
  • 3,188
  • 23
  • 34
  • Do you **have** to set this before connecting to the database or is it sufficient to set it afterwards? It seems to work either way. – Florian Walther Mar 12 '23 at 09:15
3

You can run validation while update by setting the option runValidators: true.

Example 1:


const Kitten = db.model('Kitten', kittenSchema);

const update = { color: 'blue' };
const opts = { runValidators: true };
Kitten.updateOne({}, update, opts, function() {
  // code
});

Example 2:

const Kitten = db.model('Kitten', kittenSchema);

const update = { color: 'blue' };
const opts = { runValidators: true };
Kitten.updateOne(
  {
    _id: req.params.id
  },
  {
    $set: { ...update },
  },
  opts
).then(result => {
    // code
})

Read More: https://mongoosejs.com/docs/validation.html#update-validators

Mansour Alnasser
  • 4,446
  • 5
  • 40
  • 51
2

The accepted answer does not work if you use upsert in the findOneAndUpdate options. The way around this is to create a model static method that does a findOne and then updateOne or create under the hood. create runs validation automatically.

export async function findOneAndUpdateWithValidation(
  this: LocationModel,
  filter: FilterQuery<LocationDocument>,
  update: UpdateQuery<LocationDocument>,
  options?: QueryOptions
) {
  const documentFound = await this.findOne(filter);
  
  if (!documentFound) return this.create(update);

  return this.updateOne(filter, update, options);
}

locationSchema.statics = {
  findOneAndUpdateWithValidation
}
Jamie
  • 3,105
  • 1
  • 25
  • 35
  • Jamie I am upvoting this answer as it actually solves the issue. I spent the last day obsessing on this issue and here is a Generic solution to the problem with inspiration from your work. https://gist.github.com/Corky3892/2452203657e5a31423d990faddf4ebad – Corky3892 Oct 01 '21 at 17:35
0

If you add this option in your config of mongoose it works:

mongoose.set('runValidators', true)
Krishnadas PC
  • 5,981
  • 2
  • 53
  • 54
NATALIAGJ
  • 105
  • 5
  • This is just the same as - https://stackoverflow.com/a/53856167/1682790 - (above), which was submitted two years before you. – Jack_Hu May 14 '22 at 12:27
-1

In your model, ex. Category.js file:

const CategorySchema = mongoose.Schema({
category_name : {
type : String,
required : [true, 'Category Name Is Required !'],
trim : true,
maxlength : [30, 'Category Name Is To Long !'],
unique : true,
});
const Category = module.exports = mongoose.model("Category",CategorySchema);

In your route file:

router.put("/",(req,res,next)=>{
  Category.findOneAndUpdate(
  {_id : req.body.categoryId},
  {$set : {category_name : req.body.category_name} },
  **{runValidators: true}**, function(err,result) {
    if(err){
      if(err.code === 11000){
       var duplicateValue = err.message.match(/".*"/);
       res.status(200).json({"defaultError":duplicateValue[0]+" Is Already Exsist !"});
       }else{
         res.status(200).json({"error":err.message} || {"defaultError":'Error But Not Understood !'});
       }
    }else{
     console.log("From category.js (Route File) = "+result);
     res.status(200).json({"success":"Category Updated Successfully!!"});
    }
});
Philipp Reichart
  • 20,771
  • 6
  • 58
  • 65
Deep Gandhi
  • 79
  • 1
  • 5