74

To modify a field in an existing entry in mongoose, what is the difference between using

model = new Model([...])
model.field = 'new value';
model.save();

and this

Model.update({[...]}, {$set: {field: 'new value'});

The reason I'm asking this question is because of someone's suggestion to an issue I posted yesterday: NodeJS and Mongo - Unexpected behaviors when multiple users send requests simultaneously. The person suggested to use update instead of save, and I'm not yet completely sure why it would make a difference.

Thanks!

Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
Edward Sun
  • 1,541
  • 3
  • 15
  • 26
  • 2
    I often think one of the great strengths of open source is that you can "step into" the library and see how it ticks. The Mongoose docs even have a "show source" link for many functions. – WiredPrairie Mar 09 '14 at 13:04
  • 2
    `model = new Model(...)` isn't going to update anything, because it creates a new document. I think the question would be better without that confusion. – joeytwiddle Apr 20 '16 at 06:10

4 Answers4

168

Two concepts first. Your application is the Client, Mongodb is the Server.

The main difference is that with .save() you already have an object in your client side code or had to retrieve the data from the server before you are writing it back, and you are writing back the whole thing.

On the other hand .update() does not require the data to be loaded to the client from the server. All of the interaction happens server side without retrieving to the client.So .update() can be very efficient in this way when you are adding content to existing documents.

In addition, there is the multi parameter to .update() that allows the actions to be performed on more than one document that matches the query condition.

There are some things in convenience methods that you lose when using .update() as a call, but the benefits for certain operations is the "trade-off" you have to bear. For more information on this, and the options available, see the documentation.

In short .save() is a client side interface, .update() is server side.

spinkus
  • 7,694
  • 4
  • 38
  • 62
Neil Lunn
  • 148,042
  • 36
  • 346
  • 317
  • 19
    This is a GREAT answer. You gave *context* information, which is exactly what usually is missing from top-grade guides and tutorials... – MarcoS Sep 28 '15 at 07:31
  • 1
    one other `multi` parameter I found useful, and which actually led me to this question, is `upsert` (boolean, defaults to false) which when set to true, automatically creates a new document when the query condition is not met. – dot-punto-dot Jul 10 '16 at 03:23
  • 3
    Another useful thing to know is that save() triggers the default validations while update requires you to turn runValidators on. But even in that case runValidators will only come into effect if you use certain keywords (http://mongoosejs.com/docs/validation.html#update-validators): "One final detail worth noting: update validators only run on $set and $unset operations (and $push and $addToSet in >= 4.8.0). For instance, the below update will succeed, regardless of the value of number, because update validators ignore $inc." – EDREP Aug 04 '17 at 11:41
  • 13
    **IMPORTANT NOTE** `.save()` does **not** “_write back **the whole thing**_”; it diffs the document and only sends the fields that have actually changed. So it is okay to be used to make a _tiny_ change on the database, as with `.update()`. – Константин Ван Feb 03 '18 at 01:23
  • 2
    @iss42 Here it is. https://github.com/Automattic/mongoose/blob/b5b3053fa2207b4bdea0092db5350d455accfbb1/benchmarks/benchjs/update.js#L204-L207 – Константин Ван May 01 '18 at 18:05
  • @K **You're missing the point**. In order to `.save()` you have to have retrieved the data "over the wire" and back to the client. As the the first line in the answer says, there is a distinct different in concepts. For **performance** reasons you simply call `.update()` and don't pull the data first, or have "schema hooks" or any of that, and simply tell the database what to update and it does it. Besides, you're linking to a "comment" and not the actual coded implementation, and the actual implementation of "diffing" is ... Erm not really reliable at best. So overwriting can happen. – Neil Lunn May 01 '18 at 20:15
  • Another behavior that I've noticed between the difference of `.save()` vs `.update()` is when saving, the defaults you set in your schema get added to the document if they're missing. At least as of version 5.2.4. – Perspective Jul 21 '18 at 02:42
48

Some differences:

  • As noted elsewhere, update is more efficient than find followed by save because it avoids loading the whole document.
  • A Mongoose update translates into a MongoDB update but a Mongoose save is converted into either a MongoDB insert (for a new document) or an update.
  • It's important to note that on save, Mongoose internally diffs the document and only sends the fields that have actually changed. This is good for atomicity.
  • By default validation is not run on update but it can be enabled.
  • The middleware API (pre and post hooks) is different.
Tamlyn
  • 22,122
  • 12
  • 111
  • 127
19

There is a useful feature on Mongoose called Middleware. There are 'pre' and 'post' middleware. The middlewares get executed when you do a 'save', but not during 'update'. For example, if you want to hash a password in the User schema everytime the password is modified, you can use the pre to do it as follows. Another useful example is to set the lastModified for each document. The documentation can be found at http://mongoosejs.com/docs/middleware.html

UserSchema.pre('save', function(next) {
var user = this;
// only hash the password if it has been modified (or is new)
if (!user.isModified('password')) {
    console.log('password not modified');
    return next();
}
console.log('password modified');
// generate a salt
bcrypt.genSalt(10, function(err, salt) {
    if (err) {
        return next(err);
    }

    // hash the password along with our new salt
    bcrypt.hash(user.password, salt, function(err, hash) {
        if (err) {
            return next(err);
        }
        // override the cleartext password with the hashed one
        user.password = hash;
        next();
    });
});
});
  • 4
    As of Mongoose 4.0, query middleware is also supported (e.g. for `update`). – Tamlyn Dec 03 '15 at 14:16
  • 2
    +1 Thanks for `user.isModified('password')` bit. This was driving me crazy. Every time I was trying to save something, passwords was changing. – Aamir Afridi Dec 20 '15 at 17:44
16

One detail that should not be taken lightly: concurrency

As previously mentioned, when doing a doc.save(), you have to load a document into memory first, then modify it, and finally, doc.save() the changes to the MongoDB server.

The issue arises when a document is edited that way concurrently:

  • Person A loads the document (v1)
  • Person B loads the document (v1)
  • Person B saves changes to the document (it is now v2)
  • Person A saves changes to an outdated (v1) document
  • Person A will see Mongoose throw a VersionError because the document has changed since last loaded from the collection

Concurrency is not an issue when doing atomic operations like Model.updateOne(), because the operation is done entirely in the MongoDB server, which performs a certain degree of concurrency control.

Therefore, beware!

Daniel
  • 441
  • 4
  • 10
  • In this case, are there companies that only use atomic operations like Model.updateOne() then? Or is it just something to keep in mind? I find it difficult to use atomic operations when I need to query the document first for conditional changes. Just curious if I should keep struggling to only use atomic, or be okay with using a combination of both, keeping in mind to use atomic whenever possible. – wongz Jul 02 '21 at 22:51