v145 · Web APIs · API Patterns
API Patterns
Common IndexedDB upsert patterns — idempotent writes, multi-tab safe updates, partial field merges — and when to use put() versus add().
put() is always upsert in IndexedDB: it inserts if absent, replaces if present. add() is insert-only and throws a ConstraintError if the key already exists. Use add() when duplicate prevention is the goal; use put() for everything else.
idempotent setting store
// Settings store — safe to call multiple times with same key
async function saveSetting(db, key, value) {
const tx = db.transaction('settings', 'readwrite');
tx.objectStore('settings').put({ key, value, savedAt: Date.now() });
return tx.complete ?? new Promise((r, e) => {
tx.oncomplete = r;
tx.onerror = () => e(tx.error);
});
}
// Called on every render — idempotent, no duplicate check needed
await saveSetting(db, 'volume', 0.8);
await saveSetting(db, 'volume', 0.8); // Same result, no error
multi-tab safe last-write-wins
// When multiple tabs write the same key, put() guarantees
// the last write wins — no read needed, no data corruption.
// Tab A and Tab B both call this concurrently:
async function updatePresence(db, userId, status) {
const tx = db.transaction('presence', 'readwrite');
tx.objectStore('presence').put({
id: userId,
status,
ts: Date.now(),
});
}
// One write will succeed; the other will overwrite (last-write-wins).
// This is safe — no partial write, no silent failure.
partial field merge (read-modify-write)
// When you only want to update specific fields and preserve others:
async function mergeRecord(db, store, id, patch) {
const tx = db.transaction(store, 'readwrite');
const objStore = tx.objectStore(store);
const existing = await new Promise(r => {
const req = objStore.get(id);
req.onsuccess = () => r(req.result);
});
// Merge patch into existing, then upsert
objStore.put({ ...existing, ...patch, id, updatedAt: Date.now() });
}
// Usage:
await mergeRecord(db, 'users', 'usr_123', { role: 'admin' });
// Preserves name, email, etc.; only updates role
put() vs add() decision
// put() — upsert (insert or replace)
store.put({ id: key, value }); // always succeeds
// add() — insert only (throws ConstraintError if key exists)
store.add({ id: key, value }); // fails if key already exists
// Use add() when:
// - You want to prevent accidental overwrites
// - You're creating a new record and conflict = error
// - e.g., creating a new user account (email must be unique)
// Use put() when:
// - You want idempotent writes
// - You don't care if the record exists or not
// - You're syncing or caching remote data
see also
scenario focus
Select a scenario to focus its rendered example and summary.