An Introduction to Mongoose's `save()` Function

Jun 1, 2020

Mongoose's save() function is one way to save the changes you made to a document to the database. There are several ways to update a document in Mongoose, but save() is the most fully featured. You should use save() to update a document unless you have a good reason not to.

Working with save()

save() is a method on a Mongoose document. The save() method is asynchronous, so it returns a promise that you can await on.

When you create an instance of a Mongoose model using new, calling save() makes Mongoose insert a new document.

const Person = mongoose.model('Person', Schema({
  name: String,
  rank: String
}));

const doc = new Person({
  name: 'Will Riker',
  rank: 'Commander'
});
// Inserts a new document with `name = 'Will Riker'` and
// `rank = 'Commander'`
await doc.save();

const person = await Person.findOne();
person.name; // 'Will Riker'

If you load an existing document from the database and modify it, save() updates the existing document instead.

const person = await Person.findOne();
person.name; // 'Will Riker'

// Mongoose _tracks changes_ on documents. Mongoose
// tracks that you set the `rank` property, and persists
// that change to the database.
person.rank = 'Captain';
await person.save();

// Load the document from the database and see the changes
const docs = await Person.find();

docs.length; // 1
docs[0].rank; // 'Captain'

Mongoose's change tracking sends a minimal update to MongoDB based on the changes you made to the document. You can set Mongoose's debug mode to see the operations Mongoose sends to MongoDB.

mongoose.set('debug', true);

person.rank = 'Captain';
// Prints:
// Mongoose: people.updateOne({ _id: ObjectId("...") }, { '$set': { rank: 'Captain' } })
await person.save();

Validation

Mongoose validates modified paths before saving. If you set a field to an invalid value, Mongoose will throw an error when you try to save() that document.

const Person = mongoose.model('Person', Schema({
  name: String,
  age: Number
}));

const doc = await Person.create({ name: 'Will Riker', age: 29 });

// Setting `age` to an invalid value is ok...
doc.age = 'Lollipop';

// But trying to `save()` the document errors out
const err = await doc.save().catch(err => err);
err; // Cast to Number failed for value "Lollipop" at path "age"

// But then `save()` succeeds if you set `age` to a valid value.
doc.age = 30;
await doc.save();

Middleware

Mongoose middleware lets you tell Mongoose to execute a function every time save() is called. For example, calling pre('save') tells Mongoose to execute a function before executing save().

const schema = Schema({ name: String, age: Number });
schema.pre('save', function() {
  // In 'save' middleware, `this` is the document being saved.
  console.log('Save', this.name);
});
const Person = mongoose.model('Person', schema);

const doc = new Person({ name: 'Will Riker', age: 29 });

// Prints "Save Will Riker"
await doc.save();

Similarly, post('save') tells Mongoose to execute a function after calling save(). For example, you can combine pre('save') and post('save') to print out how long save() took.

const schema = Schema({ name: String, age: Number });
schema.pre('save', function() {
  this.$locals.start = Date.now();
});
schema.post('save', function() {
  console.log('Saved in', Date.now() - this.$locals.start, 'ms');
});
const Person = mongoose.model('Person', schema);

const doc = new Person({ name: 'Will Riker', age: 29 });

// Prints something like "Saved in 12 ms"
await doc.save();

save() middleware is recursive, so calling save() on a parent document also triggers save() middleware for subdocuments.

const shipSchema = Schema({ name: String, registry: String });
shipSchema.pre('save', function() {
  console.log('Save', this.registry);
});
const schema = Schema({
  name: String,
  rank: String,
  ship: shipSchema
});
const Person = mongoose.model('Person', schema);

const doc = new Person({
  name: 'Will Riker',
  age: 29,
  ship: {
    name: 'Enterprise',
    registry: 'NCC-1701-D'
  }
});

// Prints "Save NCC-1701-D"
await doc.save();

doc.ship.registry = 'NCC-1701-E';
// Prints "Save NCC-1701-E"
await doc.save();

Want to become your team's MongoDB expert? "Mastering Mongoose" distills 8 years of hard-earned lessons building Mongoose apps at scale into 153 pages. That means you can learn what you need to know to build production-ready full-stack apps with Node.js and MongoDB in a few days. Get your copy!

More Mongoose Tutorials