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 means "store this representation at this URL". The body is the complete new state; anything you leave out is gone, not preserved:
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 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 |
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:
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 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:
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.
The fast-json-patch package applies and validates RFC 6902 patches. Dispatch on the content type with req.is():
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"] }).
PUT as a merge. Clients come to rely on it and the fix becomes a breaking change.PATCH change id or ownerId. Whitelist patchable fields and validate the result with the same schema as POST.A client sends `PUT /users/7` with only `{ "name": "Priya" }`. What should a correct server do with the existing `email` field?
PUT replaces the entire resource; omitted fields are removed, which keeps it idempotent.null deletes, arrays are replaced whole.test for conditional updates.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.