+N Consulting, Inc.

If it ain't broke - HTTP PATCH it!

Is your document “broken” inside your database? Sure, from time to time a value on a document needs to be updated, because the real world thing it describes changes. But does that mean what you currently have is “broken”? No, you say - it is not broken, just that “field xyz needs to change value”. Or “field xyz is no longer needed”.

Fine then: the document structure - the schema if you will - is still sound.

Where am I going with this? If the document is not broken, then you should not “touch” the whole document. You should use a surgical update, one that only sends a command to modify a field inside the document.

In MongoDB, the update command takes 2 forms:

  1. Document Replacement (AKA: “Slam”)
  2. Surgical Field Manipulation

In the Document Replacement form, you supply the update command with a full document value which will replace the whole document in the database. Doing so will logically target a single document (by _id or some alternate key) and supply the future document in its entirety.

db.peeps.update({_id: 'superbob'}, {name: 'bob', likes: ['coffee','bacon']}})

The update above slams in a new document with only the _ id, name, and likes fields remaining. Beyond losing any previous document field values, this update also assumes you intended to replace both the name and the likes. This may be true. But usually it’s just that in order to update one thing such as add ‘bacon’ to the list of things ‘superbob’ likes, you had to include previous unchanged values such as the name. Two problems here: One that you need to read the value from the database ahead of the update, second that the value you just read may be stale - updated by someone else - by the time your write back into the database.

The second - and my preferred - way to update a document is using surgical updates. If all I want to do is add ‘bacon’ to the list of likes, I can issue the command:

db.peeps.update({_id: 'superbob'}, {$addToSet: {likes: 'bacon'}})

This form will

  1. Add ‘bacon’ to the likes field if it doesn’t already have ‘bacon’ in it
  2. Not touch the name field, or any other field in the document
  3. Not require you to read the document before you issue the update, since you will not touch other fields.

Which brings me to the point of this post: What about REST?

REST API use HTTP actions to represent the action taken. The usual suspects are GET, POST, PUT, DELE. The least controversial one is DELE probably. GET gains some bad-boy street creds for whether queries should be solely specified using the URI path or query variables. POST and PUT generate lots of lengthy distinction discussions to decide whether PUT should or can create original objects and whether in the context of databases it is permissible to return the value of the created or modified object in full or at least the id or URI. Lots of discussions. But my focus here is on update. I’d like to avoid having to query the current document value, and I’d like to only issue a change for a subset of the fields on my entity, umm, document.

HTTP’s PATCH method aims to do exactly that. It lets a caller supply a future state of some of the fields, against a background of the existing, current entity. The docs state:

“The difference between the PUT and PATCH requests is reflected in the way the server processes the enclosed entity to modify the resource identified by the Request-URI. In a PUT request, the enclosed entity is considered to be a modified version of the resource stored on the origin server, and the client is requesting that the stored version be replaced. With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version.”

This has “Surgical Update” written all over it, and maps naturally to the intent of the surgical update. And with MongoDB, some operators are idempotent such as $addToSet, $set , $unset.

Where’s the rub?

PATCH is not always implemented as surgical update. Some API frameworks don’t explicitly support PATCH, and others implement PATCH as a PUT (read: “slam” semantics) instead of the intended surgical semantics.

Some, like Mongoose, support the surgical update semantics explicitly with the Document.prototype.update() function, or by collecting single field assignments to an existing document and issuing a concise update. Careful here! * Some frameworks will just load the document first even when PATCH is supposed to be able to handle things in a more efficient way. Other * blogs might advise you to build it yourself in the same way. This is because mongoose by default is trying to entity-track the state of the document in memory then do the math to update or create the document backing the entity.

The culprit here is not frameworks actually - it’s the consumer. Uninformed or under-curious consumers of the frameworks will be causing extra back-end round trips, concurrency issues, or even data loss. And though there are applications that would not be horribly negatively impacted by these nuanced issues, but I don’t like to risk it.

Resolving this requires that you build an update outside of the document instance itself. Create an update command and issue it directly to the underlying database. If using something like mongoose, this means calling updateOne() on the Model level, something along the lines of the code below. It does not have to find() nor save() anything ahead of updating the document and therefore saves that round-trip. Consequently also makes no assumption as to the current values in other fields, and would leave any un-mentioned field values intact.

// ... in your handler code:

// Get id and patch values from your REST somehow
const id = req.params.id; // or whatever your REST API gives you
const patchValues = JSON.parse(req.body); // eg: { age: 23}

// ... validate somehow, then:
const updateResults = await Person.updateOne({ _id: id }, patchValues);

console.log(updateResults);
// { n: 1, nModified: 1, ok: 1 } ... or something to that effect.

This handles the simple cases. To build more elaborate manipulation such as adding/removing array items or nested fields, you would want to explore more expressive PATCH parsing so that nuanced intents are clearer. You may want to take a look at jsonpatch.js for inspiration.

Why doesn’t update clarify the semantics? AFAIK: For historic reasons. To remedy this, official MongoDB drivers expose a replaceOne command (exact naming depends on language) which more precisely describes the operation when you intend to slam the existing document, leaving update and updateOne to convey surgical updates. This makes reading programs better, but does not prevent you from issuing a slam using update, so you will need to take care of it manually, especially since the update signature still allows for “slam” values to be provided.

Hopefully, this post revealed nothing new. But if you are unsure how your REST API maps to backend DB mutation commands, it’s worth taking a look.

* Examples provided to illustrate the topic of this post only. They are not in any way meant to offend or diminish the contribution of respective referenced codebase authors.

Notice

We use cookies to personalise content, to allow you to contact us, to provide social media features and to analyse our site usage. Information about your use of our site may be combined by our analytics partners with other information that you’ve provided to them or that they’ve collected from your use of their services. You consent to our cookies if you continue to use our website.