How to Use Mongoose's findOneAndUpdate Function

Oct 12, 2021

Mongoose's findOneAndUpdate() function finds the first document that matches a given filter, applies an update, and returns the document. Unlike updateOne(), findOneAndUpdate() returns the updated document. Unlike save(), findOneAndUpdate() is atomic: the document can't change between when MongoDB finds the document and when MongoDB applies the update.

Getting Started

You need at least 2 parameters to call findOneAndUpdate(): filter and update. MongoDB finds the first document that matches filter and applies update. By default, findOneAndUpdate() returns the document as it was before MongoDB applied update.

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

await Character.create({ name: 'Luke Skywalker' });

// By default, `findOneAndUpdate()` returns the document as
// it was **before** MongoDB applied the update.
const filter = { name: 'Luke Skywalker' };
const update = { rank: 'Jedi Knight' };
let doc = await Character.findOneAndUpdate(filter, update);
doc.name; // 'Luke Skywalker'
doc.rank; // undefined

// But the document is updated in the database:
doc = await Character.findOne(filter);
doc.rank; // 'Jedi Knight'

To return the document as it was after MongoDB applied the given update, you need to set the new option to true:

// If you set the `new` option to `true`, Mongoose will
// return the document with the update applied.
const filter = { name: 'Luke Skywalker' };
const update = { rank: 'Jedi Knight' };
const opts = { new: true };

let doc = await Character.findOneAndUpdate(filter, update, opts);
doc.name; // 'Luke Skywalker'
doc.rank; // 'Jedi Knight'

Upserts

There are several other options for findOneAndUpdate(). For example, you can set the upsert option to insert a new document if there isn't one that matches filter.

await Character.deleteMany({});

const filter = { name: 'Luke Skywalker' };
const update = { rank: 'Jedi Knight' };
// If you set the `upsert` option, Mongoose will insert
// a new document if one isn't found.
const opts = { new: true, upsert: true };

let doc = await Character.findOneAndUpdate(filter, update, opts);
doc.name; // 'Luke Skywalker'
doc.rank; // 'Jedi Knight'

// If `new` is `false` and an upsert happened,
// `findOneAndUpdate()` will return `null`
await Character.deleteMany({});

opts.new = false;
doc = await Character.findOneAndUpdate(filter, update, opts);
doc; // null

Middleware

Mongoose has dedicated middleware for findOneAndUpdate(). Calling findOneAndUpdate() does not fire findOne, updateOne, or save middleware. But it does fire findOneAndUpdate middleware.

const schema = Schema({
  name: String,
  rank: String
});
schema.pre('findOneAndUpdate', function middleware() {
  this.getFilter(); // { name: 'Luke Skywalker' }
  this.getUpdate(); // { rank: 'Jedi Knight' }
});
const Character = mongoose.model('Character', schema);

const filter = { name: 'Luke Skywalker' };
const update = { rank: 'Jedi Knight' };
// Mongoose calls the `middleware()` function above
await Character.findOneAndUpdate(filter, update, opts);

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!

Did you find this tutorial useful? Say thanks by starring our repo on GitHub!

More Mongoose Tutorials