Suggest an editImprove this articleRefine the answer for “What is the difference between PUT and PATCH?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**PUT vs PATCH**: PUT replaces the entire resource; PATCH updates only the fields you send. A field missing from a PUT request gets overwritten to null. ```http PATCH /users/123 {"email": "new@mail.com"} // phone stays intact ``` **Key point:** large resource, few changes - use PATCH. Full state replacement - use PUT.Shown above the full answer for quick recall.Answer (EN)Image**PUT vs PATCH** - PUT replaces the entire resource with the body you send; PATCH updates only the fields you specify. ## Theory ### TL;DR - PUT = full replacement: missing fields get overwritten to null or a schema default - PATCH = partial update: only specified fields change, the rest stay untouched - Analogy: PUT is handing over a brand new passport; PATCH is sticking a new sticker on the existing one - Idempotent: PUT always yes. PATCH - not guaranteed, depends on implementation - Rule: client holds full state, small resource → PUT. Large resource, few changes → PATCH ### Quick example ```http // PUT /users/123 - replaces the ENTIRE user object PUT /users/123 Content-Type: application/json { "id": 123, "name": "Alice", "email": "alice@new.com", "phone": null } // If original had phone: "123-456", it is now null // PATCH /users/123 - updates ONLY email, phone stays intact PATCH /users/123 Content-Type: application/json { "email": "alice@new.com" } // phone: "123-456" is preserved ``` PUT overwrites unspecified fields. PATCH leaves them alone. ### Key difference PUT treats the request body as the new authoritative state of the resource. Whatever is missing from the body gets overwritten with null or a server-defined default, per RFC 7231. PATCH applies a diff: only named fields change, everything else is preserved. This is defined in RFC 5789. The practical result is that a careless PUT destroys data with no error thrown. ### When to use - Client controls the full resource and it is small → PUT (config overwrite, CRUD form that always sends every field) - Large resource with many fields, updating just one or two → PATCH (user profiles, billing details) - Client cannot afford to fetch the full current state first → PATCH (saves bandwidth) - Need an idempotent full reset → PUT - Concurrent updates are possible → PATCH with JSON Merge Patch (RFC 7396) or JSON Patch (RFC 6902) ### Comparison table | Aspect | PUT | PATCH | |---|---|---| | **Body** | Complete resource representation | Partial changes only | | **Unspecified fields** | Overwritten (null or schema default) | Preserved | | **Idempotent** | Yes | Not guaranteed | | **Content-Type** | application/json | application/json-patch+json or application/merge-patch+json | | **RFC** | RFC 7231 | RFC 5789 | | **When to use** | Full state replacement, small resources | Sparse updates, large resources | ### How the server handles each A server receiving PUT replaces the stored resource entirely and saves the new body. Fields absent from the body get set to null or schema defaults. This is why a PUT with only a subset of fields loses the rest without any warning. For PATCH, the server applies only the named keys. Libraries like `fast-json-patch` in Node.js handle this through operations: `{"op": "replace", "path": "/email", "value": "new@mail.com"}` touches only that path in the JSON tree. ### Common mistakes **Mistake 1: using PUT for a partial update** ```javascript // Wrong: PUT omits phone, so server sets phone to null fetch('/users/1', { method: 'PUT', body: JSON.stringify({ name: 'Alice' }) }); // Result: phone is erased. No error, no warning. // Fix: use PATCH, or fetch the full object first and PUT the merged result ``` **Mistake 2: non-idempotent PATCH handler** ```javascript // Wrong: increments on every call - retrying this doubles the count app.patch('/items/:id', (req, res) => { item.count += req.body.amount; }); // Right: replace the value directly - same result on every retry app.patch('/items/:id', (req, res) => { item.count = req.body.count; }); ``` **Mistake 3: wrong Content-Type for PATCH** ```javascript // Wrong: some servers treat application/json as a full PUT headers: { 'Content-Type': 'application/json' } // Correct for JSON Patch ops array: headers: { 'Content-Type': 'application/json-patch+json' } // Correct for merge patch (object diff, null means delete): headers: { 'Content-Type': 'application/merge-patch+json' } ``` **Mistake 4: PUT without If-Match in concurrent environments** A blind PUT overwrites changes made by another client between your read and your write. Add an ETag and send `If-Match: "abc123"` with the PUT. The server returns 412 Precondition Failed if the resource changed, forcing a re-fetch. ### Real-world usage - **Firebase**: `update()` = PATCH semantics (preserves fields), `set()` = PUT semantics - **Stripe API**: `PATCH /customers/{id}` for sparse billing updates - **Strapi CMS**: uses JSON Patch for partial content updates - **React Query / SWR**: `useMutation` with PATCH for optimistic UI - **Express.js + MongoDB**: `replaceOne` for PUT (full replace), `updateOne` with `$set` for PATCH ### Follow-up questions **Q:** Why is PUT idempotent but simple PATCH not always? **A:** PUT uses the full body as the new state, so ten identical requests produce the same result. Simple merge-patch is also idempotent when it replaces a value. The problem is additive logic (increment, append) inside the handler. Use JSON Patch with explicit `replace` operations to guarantee idempotence. **Q:** What is the difference between JSON Patch and JSON Merge Patch? **A:** JSON Patch (RFC 6902) is an array of operations: `add`, `remove`, `replace`, `test`. JSON Merge Patch (RFC 7396) is a simpler object diff where `null` means delete the field. Merge Patch is easier to write but lacks operation ordering and atomic test-and-set support. **Q:** How do you design a safe PATCH system for concurrent editing? **A:** Use ETags and the `If-Match` header. The client reads the resource and gets an ETag, then sends PATCH with `If-Match: "<etag>"`. If someone else changed the resource, the server returns 412. The client re-fetches and reapplies. For collaborative editing, add JSON Patch `test` operations to assert preconditions before each mutation. **Q:** When does PUT break idempotence? **A:** When the server generates or modifies fields on every write regardless of body, like `updatedAt` timestamps. The body is identical but the stored resource differs after each call. ## Examples ### Basic: updating a user profile ```javascript // PUT - must include ALL fields or data is lost app.put('/users/:id', async (req, res) => { // replaceOne overwrites the entire document await User.replaceOne({ _id: req.params.id }, req.body); res.json(await User.findById(req.params.id)); }); // PATCH - send only what changed app.patch('/users/:id', async (req, res) => { // $set with partial body; unmentioned fields stay intact await User.updateOne({ _id: req.params.id }, { $set: req.body }); res.json(await User.findById(req.params.id)); }); ``` PUT uses `replaceOne`, which overwrites the whole document. PATCH uses `updateOne` with `$set`, so only the sent fields change. I have seen junior devs ship PUT handlers that quietly erased phone numbers for weeks before anyone noticed. ### Intermediate: JSON Patch with concurrent safety ```javascript const jsonPatch = require('fast-json-patch'); app.patch('/users/:id', async (req, res) => { const user = await User.findById(req.params.id); const clientEtag = req.headers['if-match']; if (clientEtag && clientEtag !== user.etag) { return res.status(412).json({ error: 'Precondition Failed' }); } // req.body is a JSON Patch ops array const patched = jsonPatch.applyPatch(user.toObject(), req.body).newDocument; await User.replaceOne({ _id: req.params.id }, patched); res.json(patched); }); // Client sends: // PATCH /users/123 // If-Match: "etag-abc" // Content-Type: application/json-patch+json // [{ "op": "replace", "path": "/email", "value": "new@mail.com" }] ``` The `test` operation adds another safety layer. `{"op": "test", "path": "/email", "value": "old@mail.com"}` throws before the `replace` if the current value does not match, blocking stale writes before they happen.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.