REST API Partial Updates with PATCH
Users rarely change a whole record at once. A person edits a phone number, toggles a setting, or fixes a typo. Sending the entire resource for such a tiny edit wastes bandwidth and risks overwriting other fields. PATCH solves this problem by sending only the changes.
The Form Editing Picture
Imagine a printed form with ten fields. PUT asks you to rewrite the whole form with a fresh copy. PATCH lets you cross out one field and write the new value beside it. The rest of the form stays untouched.
PUT and PATCH Side by Side
Stored user: { "name": "Ravi", "email": "ravi@mail.com", "city": "Pune" }
PUT /users/9 body: { "name": "Ravi", "email": "ravi@mail.com", "city": "Delhi" }
→ client must send every field
PATCH /users/9 body: { "city": "Delhi" }
→ client sends only the change
A PUT request that leaves out a field may erase it. A PATCH request leaves unmentioned fields alone.
Two Patch Formats
JSON Merge Patch
JSON Merge Patch uses the media type application/merge-patch+json. The body looks like a smaller version of the resource. The server merges it into the stored data. A null value removes a field.
PATCH /users/9
Content-Type: application/merge-patch+json
{ "city": "Delhi", "nickname": null }
This format reads easily and suits most projects. It cannot change single items inside an array, because it replaces the whole array.
JSON Patch
JSON Patch uses the media type application/json-patch+json. The body holds a list of operations, each with an op, a path, and sometimes a value.
PATCH /users/9
Content-Type: application/json-patch+json
[
{ "op": "replace", "path": "/city", "value": "Delhi" },
{ "op": "add", "path": "/tags/-", "value": "premium" },
{ "op": "remove", "path": "/nickname" }
]
Operations include add, remove, replace, move, copy, and test. This format offers precise control over arrays and nested objects at the cost of extra complexity.
How the Server Handles a Patch
Request arrives
│
▼
Load current resource ──→ Apply changes ──→ Validate result
│
┌───────────────────────┴───────────┐
▼ ▼
Valid: save Invalid: reject
return 200 or 204 return 422 or 400
Validation runs on the final merged resource, not on the patch alone. A patch that sets an end date before the stored start date must fail even though the patch looks fine by itself.
Status Codes
- 200 OK: the update worked, and the body shows the new resource.
- 204 No Content: the update worked, and the response has no body.
- 400 Bad Request: the patch document has a syntax problem.
- 404 Not Found: the target resource does not exist.
- 415 Unsupported Media Type: the server does not accept the patch format.
- 422 Unprocessable Content: the patch is valid, and the resulting data breaks a rule.
Protecting Against Lost Updates
Two users may edit the same record at the same moment. Without protection, the last save wins and silently erases the other change. An ETag header solves this. The server sends a version tag with each read. The client returns it in an If-Match header with the patch. The server rejects the request with 412 Precondition Failed when the stored version has moved on.
GET /users/9 → ETag: "v5" PATCH /users/9 If-Match: "v5" → 200 OK, new ETag "v6" PATCH /users/9 If-Match: "v5" → 412 Precondition Failed
Idempotency of PATCH
A patch that sets city to Delhi gives the same result on every repeat. A patch that adds 1 to a counter changes the value on every repeat. Design patches as "set to this value" instructions whenever possible.
Common Mistakes
Using PATCH Like PUT
Requiring all fields in a PATCH body defeats the purpose. Accept any valid subset.
Allowing Protected Fields
A client should not patch fields such as id, createdAt, or role. Ignore or reject changes to read-only fields.
