REST API ETags: Prevent One Edit From Overwriting Another

REST APIs
HTTP
ETags
Laravel
Concurrency

By Kashii Malik ? Updated

Use ETags and If-Match to reject stale edits, enforce the version check in Laravel, and give users a clear way to resolve conflicts.

REST API ETags: Prevent One Edit From Overwriting Another

Two staff members open the same inventory record. It shows 12 units. One receives a delivery and updates the count to 17. The other finishes a count based on the old screen and saves 11. Both requests succeed, but the second save silently wipes out the first.

If you control the REST API, you can make the second save stop and ask for a fresh look. Return a version of the record as an ETag, then require clients to send it back in If-Match when they edit. The database must enforce that check at the moment it writes.

Send the version the editor actually saw

On GET /inventory/42, return the count and a strong ETag such as "stock-42-v7". The editor should keep that tag alongside the displayed data. When the first employee saves 17, send PATCH with If-Match: "stock-42-v7". If the record is still on version 7, save the new count, advance the version to 8, and return the updated representation and tag.

The second employee’s request still carries version 7. The server compares it with the current version and returns 412 Precondition Failed without applying the stale update. RFC 9110’s If-Match section describes strong comparison and conditional requests. Keep the tag quoted and avoid a weak W/ prefix for this use. The example assumes one JSON representation; if the API serves different representations, make sure its validators distinguish them where required.

An open carton of small product boxes beside two sealed cartons on a worn metal shelf. Fictional AI-generated editorial image.

An open carton of small product boxes beside two sealed cartons on a worn metal shelf. Fictional AI-generated editorial image.

Fictional editorial image: cartons on a stockroom shelf.

Check and write in the same database operation

A controller that reads the current version, compares it in PHP, and then saves without a condition still has a race. Another request can update the row between the comparison and the write. Put the record ID and expected version in the update’s WHERE clause and increment the version as part of that same update.

In Laravel, use the query builder to scope the row to the authorized account and expected version, then update the validated fields and increment the version. The Laravel query builder documentation explains that update returns the number of affected rows. In this design, one affected row means the change won. Zero means the write did not apply; distinguish a stale version from a missing or inaccessible record using your API’s normal resource and authorization rules.

Every write path that changes the protected representation needs to advance its version. If an import or admin screen updates stock without doing so, the API can accept an edit based on an old count. Check these paths before relying on the tag.

Give people a way to resolve the conflict

If the endpoint requires a precondition and the client omitted it, RFC 6585’s 428 Precondition Required gives you a response for that case. Use a clear error message explaining that the client must load the record and submit its version. A supplied tag that no longer matches is a different case: return 412 and preserve the user’s draft on the client.

For the inventory example, show “You started with 12, the current count is 17, and your draft says 11.” Ask the employee to confirm whether 11 was a fresh physical count or an adjustment of minus one. If the action really means “subtract one,” a purpose-built adjustment endpoint can apply that change to the current value, subject to business rules and protection against duplicate submissions. Don’t automatically fetch a fresh tag and replay an absolute overwrite.

Test the conflict before enabling the rule

  • Open the same record in two sessions. Save from one and confirm that the second receives 412 without overwriting it.
  • Send a write without If-Match and verify that the response explains how to retry with a valid precondition.
  • Make a change through the import or admin path, then submit the older API tag. Confirm the stale edit is rejected.
  • Run simultaneous saves with the same tag. Exactly one should win when both are valid and target the same version.
  • Check that the rejected editor retains its unsaved input and can reload the latest record for review.

For related internal operations work, see my Maven Pro intranet project. If staff are overwriting each other’s edits in an admin tool, I can help trace the write paths and decide where a version check or an adjustment endpoint makes sense.

Share this post:

Related Posts

Track Video Lesson Progress in Laravel Without Losing the Resume Point
November 10, 2026

Track Video Lesson Progress in Laravel Without Losing the Resume Point

Design a Laravel course progress model that saves a learner’s place across devices, handles delayed updates, and keeps completion separate.

Google Maps Route Matrix: Compare Driver ETAs for Dispatch
November 9, 2026

Google Maps Route Matrix: Compare Driver ETAs for Dispatch

Compare driving times from available vehicles to a pickup, handle missing estimates, and keep Route Matrix requests small.

Laravel Booking Forms: Prevent Double Bookings With Database Locks
November 4, 2026

Laravel Booking Forms: Prevent Double Bookings With Database Locks

A practical Laravel design for limited booking capacity: lock the slot, allocate atomically, manage payment holds, and test competing requests.