I've always used updateOne
for updating individual documents in my MongoDB databases.
And I still do - when I just need a simple update.
But lately, I've found myself using findOneAndUpdate
much more. Especially when building an API where I need to return the updated content from the back-end to the front-end client.
So let's look at the differences between findOneAndUpdate
and updateOne
- and when you would use them.
updateOne
- Updates a document
- Can insert a document (with
upsert
)
updateOne
updates a document in MongoDB. It can also insert a document with the upsert: true
option.
If you just need to update a document in MongoDB, this is the command you will want to use.
await db.collection('users').updateOne({ user: userId }, {
$set: { name: "codemzy" },
});
🙏 Please, please, please, remember to use $set
or one of the other field update operators - if you forget, your update could override the entire document (not just the field(s) you want to update!).
But what if you want to update a document and then return the updated document to the user? Do you need to run a findOne
command before the update?
Or if you want to know what a field value was before the update? Do you need to run a findOne
command after the update?
Noooo!
For either of these use cases, you need findOneAndUpdate
.
findOneAndUpdate
- Updates a document
- Can insert a document (with
upsert
) - Returns the document either before or after the update
findOneAndUpdate
does everything that updateOne
does, plus returns the document (either before or after the update depending on the returnDocument
option.
By default returnDocument
is "before", which means the document is returned before the update. I guess this makes sense, since the command is findOneAndUpdate
, not updateAndFindOne
.
So it finds the document first, and then updates.
But you can also get the updated document returned, by setting returnDocument
to "after", like this:
let result = await db.collection('users').findOneAndUpdate({ user: userId }, {
$set: { name: "codemzy" },
}, { returnDocument: "after" });
console.log(result.value.name);
// "codemzy"
Let's look at a couple more use cases, and how findOneAndUpdate
can handle them.
Knowing a field value before an update
Sometimes, you might need to update a document but still know what the value was before.
For example, we might want to log what the update was.
Let's imagine a user is changing the price of a product. We might want to log what the price was before the change so that we have a record for any customer queries.
Instead of doing a findOne
to get the value and then doing the update, we can use findOneAndUpdate
to do this in one call to the database. Pretty cool!
By default, findOneAndUpdate
returns the document before the update, so this works out of the box.
let result = await db.collection('users').findOneAndUpdate({ product: productId }, {
$set: { price: newPrice },
}, { returnDocument: "after" });
if (result.value) {
console.log(`Pricing of product ${productId} changed from ${result.value.price} to ${newPrice}.`);
}
Another good trick if you are using the upsert
option, is that result.value
will be null
if there was nothing to update and the command created (upserted) a new document. This is how you can know if findOneAndUpdate
upserted or updated.
Getting a document after an update
Sometimes, you might need to update a field in a document, but return the entire document (or a different field value) after the update.
For example, let's imagine you use the same command to perform all user updates, and conditionally add properties to an updates object.
let updates = {
...(newEmail && {email: newEmail}),
...(newName && {name: newName}),
...(newAddress && {address: newAddress}),
};
When you run your findOneAndUpdate
, you don't know which fields have been updated. Maybe this time the user updated just their name, or their name and their email, or any other combination of updates.
Instead of figuring all that out, we can use findOneAndUpdate
to return the document after the update.
let result = await db.collection('users').findOneAndUpdate({ user: userId }, {
$set: updates,
}, { returnDocument: "after" });
Now we know that result.value
contains the latest version of the user, with all of the updates. We can return that data to the front-end, or email the user with a confirmation, any we know we have the correct up-to-date information.
I didn't cover it much in this blog post, but if you want to learn more about the upsert
option, I have a blog post on MongoDB findOneAndUpdate
and how to know if it's an upsert.