Post

Changes to findOneAnd* APIs in Node.js Driver 6.0.0

Cross posted from the MongoDB Blog

One exciting change that is coming in release 6.0.0 of the Node.js Driver is that 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.

1
2
3
4
5
6
7
8
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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
  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, instead you would get the output above.

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.

1
2
3
4
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);
1
{ 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 that the operation was executed against. As we would expect, when the includeResultMetadata is changed to false, inline validation will indicate there’s an issue since the value property no longer exists on the type associated with the result

Attempting to compile our TypeScript project will also fail.

1
2
3
4
5
6
7
8
9
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);
                                ~~~~~

This makes it incredibly easy to identify where in the code changes need to be made.

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.

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.