Skip to content

Cookbook: conditional writes

Eugene Lazutkin edited this page Jul 3, 2026 · 2 revisions

Cookbook: conditional writes & idempotency

double-meh lowers REST-correctness intents to the right headers. Background: Requests & options, Response envelope.

Optimistic concurrency (If-Match)

Send the ETag you last saw; the server rejects with 412 if it changed underneath you:

const {data, etag} = await io.full.get('/things/7');
await io.put('/things/7', {...data, name: 'new'}, {ifMatch: etag});

Read-modify-write with automatic conflict-retry

io.update does the whole loop for you — GET + ETag, apply your function, conditional PUT, and on a 412 re-read and re-apply (up to a few times), then invalidate the cache:

await io.update('/things/7', thing => ({...thing, views: thing.views + 1}));

If the GET returns no ETag, io.update throws an IOError — there is nothing to make the PUT conditional on. Pass {force: true} to update unconditionally anyway:

await io.update('/things/7', fn, {force: true}); // no ETag → plain PUT, last write wins

Your function may be async and may return undefined to mean "no change" (a no-op that resolves to the current data). See helpers.

Create-only (If-None-Match: *)

Fail instead of overwriting if the resource already exists:

await io.put('/things/7', thing, {ifNoneMatch: '*'});

Idempotent POST

A bare POST is not auto-retried (it is not safe to replay). Add an idempotency key and the same key is reused across retries, so the server can dedupe:

await io.post('/payments', payment, {idempotencyKey: true, retry: true});
//                                    ^ true → a generated UUID, stable across retries

Pass a string to supply your own key.

Cache revalidation (If-None-Match: <etag>)

The cache — on by default for GETs — revalidates automatically: a stale entry is re-requested with If-None-Match: <stored etag>, and a 304 Not Modified refreshes the entry (merging the fresh headers) and serves the stored body:

await io.get('/things/7'); // stale entry → conditional GET; 304 → the cached body, no re-download

See also

Clone this wiki locally