Do you use the MongoDB Node.js driver? If so, there’s a good chance you use various find() operations regularly.
MongoDB plans to release version 6.0.0 of the Node.js driver in August 2023, and we’ve made some exciting improvements to the findOneAnd*
operation. With the new driver release, the modified (or original) document targeted by a findOneAnd*
operation will now be returned by default.
Current state
Up until now, as opposed to returning the requested document, this family of API methods would return a ModifyResult
, which would contain the requested document in a value
field. This design was due to these APIs leveraging the MongoDB Server’s findOneAndModify
command and wrapping the command’s output directly.
To demonstrate, let’s adapt the code from the Driver’s documented usage examples to update one document in our movies collection using the findOneAndUpdate
API.
const database = client.db("sample_mflix");
const movies = database.collection("movies");
// Query for a movie that has the title 'The Room'
const query = { title: "The Room" };
const updatedMovie = await movies.findOneAndUpdate(query,
 { $set: { "imdb.rating": 3.4, "imdb.votes": 25750 } },
 { projection: { _id: 0, title: 1, imdb: 1 }, returnDocument: "after" });
console.log(updatedMovie);

{
 lastErrorObject: { n: 1, updatedExisting: true },
 value: {
 title: 'The Room',
 imdb: { rating: 3.4, votes: 25750, id: 368226 }
 },
 ok: 1,
 '$clusterTime': {
 clusterTime: new Timestamp({ t: 1689343889, i: 2 }),
 signature: {
 hash: Binary.createFromBase64("3twlRKhDSGIW25WVHZl17EV2ulM=", 0),
 keyId: new Long("7192273593030410245")
 }
 },
 operationTime: new Timestamp({ t: 1689343889, i: 2 })
}

One of the options we set was a returnDocument
of after
, which should return the updated document. Though the expectation may be that the function call would return the document directly, as we can see this isn’t the case.
While the document you’re looking for can be accessed using updatedMovie.value
, that isn’t the most intuitive experience. But changes are on the way!
What can we do right now?
Starting with the Node.js Driver 5.7.0 release a new FindOneAnd*Options
property called includeResultMetadata
has been introduced. When this property is set to false
(default is true
) the findOneAnd*
APIs will return the requested document as expected.
const updatedMovie = await movies.findOneAndUpdate(query,
 { $set: { "imdb.rating": 3.3, "imdb.votes": 25999 } },
 { projection: { _id: 0, title: 1, imdb: 1 }, includeResultMetadata: false });
console.dir(updatedMovie);

{ title: 'The Room', imdb: { rating: 3.3, votes: 25999, id: 368226 } }

What about TypeScript?
If your application uses TypeScript and the MongoDB Node.js Driver, anywhere a findOneAnd*
call is made, if the requested document is required it will be accessed via the value property of the ModifyResult
. This occurs when includeResultMetadata
is not set or when it is set to true
(the current default value).
Type hinting will indicate the Schema associated with the collection the operation was executed against. As we would expect, when the includeResultMetadata
is changed to false
inline validation will indicate there’s an issue as the value
property no longer exists on the type associated with the result.
Attempting to compile our TypeScript project will also fail.
TSError: ⨯ Unable to compile TypeScript:
index.ts:31:17 - error TS18047: 'updatedMovie' is possibly 'null'.

31 console.dir(updatedMovie.value);
 ~~~~~~~~~~~~
index.ts:31:30 - error TS2339: Property 'value' does not exist on type 'WithId<Movie>'.

31 console.dir(updatedMovie.value);

Next Steps
If you’re using the findOneAnd*
family of APIs in your JavaScript or TypeScript project, upgrading the MongoDB Node.js Driver to 5.7.0+ and adding the includeResultMetadata: false
option to those API calls will allow you to adapt your application to the new behavior prior to the 6.0.0 release.
Once 6.0.0 is released, includeResultMetadata: false
will become the default behavior. If your application relies on the previous behavior of these APIs, setting includeResultMetadata: true
will allow you to continue to access the ModifyResult
directly.