Partial Updates: PUT vs PATCH, JSON Merge Patch and JSON Patch

Intermediate
12 min

Partial Updates: PUT vs PATCH, JSON Merge Patch and JSON Patch

Most real updates touch one or two fields: a price change, a status flip. Sending the whole resource back is wasteful and can overwrite someone else's changes. In this lesson you will learn what PUT promises, how PATCH differs, and how to implement partial updates with the two standard patch formats.

PUT Replaces the Whole Resource

PUT means "store this representation at this URL". The body is the complete new state; anything you leave out is gone, not preserved:

json
PUT /products/42 Content-Type: application/json { "name": "Mechanical Keyboard", "price": 5999 }

If the product previously had a description and tags, a correct PUT removes them: the client said the resource now consists of only name and price. That keeps PUT idempotent and easy to reason about, but a client that forgets a field loses data. Validate a PUT body as a complete resource: a missing required field is a 422, not something to copy from the old record.

PATCH Applies a Set of Changes

PATCH sends a description of changes rather than a full representation. HTTP does not define that description; the Content-Type header names the patch format. Two formats are standardized:

| Format | Content-Type | Best for | |---|---|---| | JSON Merge Patch (RFC 7396) | application/merge-patch+json | Simple "set these fields" updates | | JSON Patch (RFC 6902) | application/json-patch+json | Precise edits, arrays, conditional updates |

JSON Merge Patch

A merge patch looks like the part of the resource you want to change. Included fields are set, omitted fields are untouched, and a field set to null is removed:

json
PATCH /products/42 Content-Type: application/merge-patch+json { "price": 5499, "description": null, "tags": ["sale"] }

Result: price updated, description deleted, tags replaced entirely. Two limitations follow: you cannot store a null value, and you cannot edit one array element without resending the array.

JSON Patch

JSON Patch is an ordered list of operations addressed by JSON Pointers such as /tags/0 (first element) or /tags/- (append). The operations are add, remove, replace, move, copy and test:

json
PATCH /products/42 Content-Type: application/json-patch+json [ { "op": "test", "path": "/price", "value": 5999 }, { "op": "replace", "path": "/price", "value": 5499 }, { "op": "add", "path": "/tags/-", "value": "sale" }, { "op": "remove", "path": "/description" } ]

The patch is atomic: if any operation fails, none are kept. test is the standout feature: here it says "only apply this if the price is still 5999", protecting against a colleague who changed it a second ago. Answer a failed test with 409 Conflict.

Implementing Both in Express

The fast-json-patch package applies and validates RFC 6902 patches. Dispatch on the content type with req.is():

javascript
const { applyPatch } = require("fast-json-patch"); app.patch("/products/:id", (req, res) => { const product = db.get(req.params.id); if (!product) return res.status(404).json({ error: "Product not found" }); if (req.is("application/json-patch+json")) { try { const result = applyPatch(product, req.body, true, false); // validate, no mutation db.set(req.params.id, result.newDocument); return res.json(result.newDocument); } catch (err) { return res.status(422).json({ error: "Invalid patch", detail: err.message }); } } if (req.is("application/merge-patch+json")) { const merged = { ...product, ...req.body }; Object.keys(req.body).forEach((k) => req.body[k] === null && delete merged[k]); db.set(req.params.id, merged); return res.json(merged); } res.status(415).set("Accept-Patch", "application/merge-patch+json, application/json-patch+json").end(); });

express.json() only parses application/json by default, so register it as express.json({ type: ["application/json", "application/*+json"] }).

Common Mistakes

  • Implementing PUT as a merge. Clients come to rely on it and the fix becomes a breaking change.
  • Letting PATCH change id or ownerId. Whitelist patchable fields and validate the result with the same schema as POST.
Quick Quiz
Question 1 of 2

A client sends `PUT /users/7` with only `{ "name": "Priya" }`. What should a correct server do with the existing `email` field?

Key Takeaways

  • PUT replaces the entire resource; omitted fields are removed, which keeps it idempotent.
  • JSON Merge Patch is simple: included fields are set, null deletes, arrays are replaced whole.
  • JSON Patch is an atomic list of operations with paths, including test for conditional updates.
  • Advertise supported formats with Accept-Patch, reject others with 415, and validate the result.

Next lesson: Designing a Complete CRUD API — combine methods, URLs, status codes and bodies into one coherent design.

Partial Updates: PUT vs PATCH, JSON Merge Patch and JSON Patch - REST APIs | CodeYourCraft | CodeYourCraft