Web APIs Cheatsheet

Storage (localStorage, Cookies, IndexedDB)

Use this Web APIs reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

localStorage and sessionStorage

Both implement the Storage interface. localStorage persists across sessions; sessionStorage is scoped to the browser tab.

// Set
localStorage.setItem('key', 'value');
localStorage.setItem('obj', JSON.stringify({ a: 1 }));

// Get
localStorage.getItem('key');          // 'value' or null
JSON.parse(localStorage.getItem('obj')); // { a: 1 }

// Remove
localStorage.removeItem('key');
localStorage.clear();                 // remove all

// Enumerate
localStorage.length;                  // number of items
localStorage.key(0);                  // key at index
for (let i = 0; i < localStorage.length; i++) {
  const k = localStorage.key(i);
  console.log(k, localStorage.getItem(k));
}
// or
Object.entries(localStorage);         // all [key, value] pairs

Storage API Quick Reference

Method / PropertyDescription
.setItem(key, value)Store string value
.getItem(key)Retrieve string or null
.removeItem(key)Delete entry
.clear()Delete all entries
.key(index)Get key at numeric index
.lengthNumber of stored items

Storage Event

// Fires on OTHER tabs in the same origin (not the writing tab)
window.addEventListener('storage', e => {
  e.key;          // changed key (null if clear() called)
  e.oldValue;     // previous value string
  e.newValue;     // new value string (null if removed)
  e.url;          // URL of the page that changed storage
  e.storageArea;  // localStorage or sessionStorage reference
});

Gotchas

  • Only strings are stored. Always JSON.stringify / JSON.parse for objects.
  • localStorage is synchronous and blocks the main thread on large reads/writes — prefer IndexedDB for bulk data.
  • Quota is ~5–10 MB per origin. Exceeds throw DOMException: QuotaExceededError.
  • Not available in file:// origins or private browsing in some browsers.
  • sessionStorage is NOT shared between tabs, even on the same origin.

Cookies

Reading and Writing

// Read all cookies as a single string
document.cookie; // 'name=Alice; theme=dark'

// Parse cookies
const cookies = Object.fromEntries(
  document.cookie.split('; ').map(c => c.split('=').map(decodeURIComponent))
);
cookies.name; // 'Alice'

// Set a cookie (one at a time)
document.cookie = 'theme=dark';
document.cookie = `token=${encodeURIComponent(val)}; path=/; max-age=86400; SameSite=Lax; Secure`;

// Delete (set max-age=0 or expires in past)
document.cookie = 'theme=; max-age=0; path=/';

Cookie Attributes

AttributeDescription
path=/Which paths the cookie is sent on (default: current dir)
domain=example.comDomain scope (leading dot covers subdomains)
max-age=NLifetime in seconds; overrides expires
expires=DATEExpiry date string (UTC)
SecureHTTPS only
HttpOnlyNot accessible from JS (set by server)
SameSite=StrictNever sent cross-site
SameSite=LaxSent on top-level navigations (default in modern browsers)
SameSite=None; SecureAlways sent cross-site (requires Secure)

Cookie Store API (async, modern)

// Read
const cookie = await cookieStore.get('token');
cookie?.value;

const all = await cookieStore.getAll();

// Write
await cookieStore.set({ name: 'theme', value: 'dark', maxAge: 86400 });

// Delete
await cookieStore.delete('theme');

// Change events (ServiceWorker and page context)
cookieStore.addEventListener('change', e => {
  e.changed; // array of changed cookies
  e.deleted; // array of deleted cookies
});

IndexedDB

Asynchronous, transactional, key-value/object-store DB. Quota is typically 50%+ of disk. Supports complex queries via indexes.

Open and Upgrade

const request = indexedDB.open('MyDB', 2 /* version */);

request.onupgradeneeded = e => {
  const db = e.target.result;
  const oldVersion = e.oldVersion;

  if (oldVersion < 1) {
    const store = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
    store.createIndex('by-email', 'email', { unique: true });
    store.createIndex('by-name', 'name', { unique: false });
  }
  if (oldVersion < 2) {
    db.createObjectStore('settings', { keyPath: 'key' });
  }
};

request.onsuccess = e => {
  const db = e.target.result;
  db.onversionchange = () => { db.close(); location.reload(); };
};
request.onerror = e => console.error(e.target.error);

CRUD Operations

// Helper: wrap IDB request in a Promise
function idbRequest(req) {
  return new Promise((res, rej) => {
    req.onsuccess = () => res(req.result);
    req.onerror = () => rej(req.error);
  });
}

// Add / Put
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
const id = await idbRequest(store.add({ name: 'Alice', email: 'a@a.com' }));
await idbRequest(store.put({ id, name: 'Alice B', email: 'a@a.com' })); // upsert
await tx.done; // only available in idb library; else use tx.oncomplete

// Get
const tx2 = db.transaction('users', 'readonly');
const user = await idbRequest(tx2.objectStore('users').get(id));

// Get by index
const byEmail = await idbRequest(
  tx2.objectStore('users').index('by-email').get('a@a.com')
);

// Delete
const tx3 = db.transaction('users', 'readwrite');
await idbRequest(tx3.objectStore('users').delete(id));

// Clear store
await idbRequest(tx3.objectStore('users').clear());

Cursors and Range Queries

const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const index = store.index('by-name');

// IDBKeyRange
IDBKeyRange.only('Alice');
IDBKeyRange.lowerBound('A', false);   // false = inclusive
IDBKeyRange.upperBound('Z', true);    // true = exclusive
IDBKeyRange.bound('A', 'M', false, true);

// Cursor
const req = index.openCursor(IDBKeyRange.bound('A', 'M'));
req.onsuccess = e => {
  const cursor = e.target.result;
  if (!cursor) return; // done
  console.log(cursor.key, cursor.value);
  cursor.continue();   // next
  // cursor.advance(2) — skip 2
  // cursor.update({...cursor.value, active: true})
  // cursor.delete()
};

// getAll (no cursor overhead)
const all = await idbRequest(store.getAll());
const range = await idbRequest(store.getAll(IDBKeyRange.lowerBound(10), 50 /* limit */));
const keys = await idbRequest(store.getAllKeys());
const count = await idbRequest(store.count());

Transactions

const tx = db.transaction(['users', 'settings'], 'readwrite');
tx.oncomplete = () => console.log('committed');
tx.onerror = e => console.error(e.target.error);
tx.onabort = () => console.log('aborted');
tx.abort(); // manually roll back

// Transactions auto-commit when no more requests are queued
// Do NOT await non-IDB promises inside a transaction — it will close

idb Library (recommended wrapper)

import { openDB } from 'idb'; // npm install idb

const db = await openDB('MyDB', 1, {
  upgrade(db) {
    db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
  },
});

await db.add('notes', { text: 'Hello', created: Date.now() });
const note = await db.get('notes', 1);
const all = await db.getAll('notes');
await db.put('notes', { id: 1, text: 'Updated' });
await db.delete('notes', 1);

// Transaction shorthand
const tx = db.transaction('notes', 'readwrite');
await tx.store.add({ text: 'New' });
await tx.done;

Cache API (Service Worker)

// Open / create cache
const cache = await caches.open('v1');

// Add requests
await cache.add('/index.html');
await cache.addAll(['/style.css', '/app.js']);

// Match
const res = await cache.match('/index.html');

// Put (manual)
const freshRes = await fetch('/data.json');
await cache.put('/data.json', freshRes.clone());

// Delete
await cache.delete('/old.js');

// List all cache names
const names = await caches.keys();

// Delete old caches
await caches.delete('v0');

// Check if a URL is cached
const hit = await caches.match('/style.css');

Storage Quota API

const { usage, quota } = await navigator.storage.estimate();
console.log(`${(usage / quota * 100).toFixed(1)}% used`);

// Request persistent storage (won't be evicted automatically)
const granted = await navigator.storage.persist();
const isPersisted = await navigator.storage.persisted();