Chapter 25 of 28 · JavaScript for Beginners — Complete Free Tutorial

Browser Storage & Web APIs

Browsers give JavaScript memory beyond the session: localStorage for preferences that persist, sessionStorage for tab-scoped state, cookies for server-readable data — plus a family of Web APIs (clipboard, history, geolocation, media). This chapter covers choosing the right store, the JSON round-trip, quotas, and the APIs you will actually use.

Choosing a store

StoreLifetimeSent to serverTypical use
localStorageUntil cleared (years)NoPreferences, drafts, onboarding state
sessionStorageUntil the tab closesNoMulti-step form state, tab-local data
CookiesSet expiryYes — every requestAuth sessions, server-required data
IndexedDBUntil clearedNoLarge/structured data (offline apps)
Memory (variables)Page lifetimeNoEverything else

Size: ~5 MB per origin for Web Storage, ~4 KB per cookie. Storage is string-only for localStorage/sessionStorage — hence JSON.

localStorage basics

localStorage.setItem('theme', 'dark');
console.log(localStorage.getItem('theme'));    // → 'dark'
console.log(localStorage.getItem('nope'));     // → null (not undefined)

localStorage.removeItem('theme');
localStorage.setItem('visit_' + Date.now(), '1');
console.log(localStorage.length);
localStorage.clear();                          // everything for this origin

Storing objects and arrays (JSON round-trip)

const cart = { items: [{ id: 1, qty: 2 }], updated: Date.now() };

localStorage.setItem('cart', JSON.stringify(cart));

const saved = JSON.parse(localStorage.getItem('cart') ?? '{}');
console.log(saved.items[0].id);   // → 1

Strings only — so every write is JSON.stringify and every read is JSON.parse. Wrap reads in try/catch: a corrupted value throws.

A tiny safe wrapper (do this once, reuse forever)

export const store = {
  get(key, fallback = null) {
    try {
      const raw = localStorage.getItem(key);
      return raw === null ? fallback : JSON.parse(raw);
    } catch {
      return fallback;                     // corrupted entry → default
    }
  },
  set(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch (err) {
      console.error('storage full?', err);  // QuotaExceededError
      return false;
    }
  },
  remove(key) { localStorage.removeItem(key); },
};

store.set('prefs', { lang: 'en', compact: true });
const prefs = store.get('prefs', { lang: 'en' });

Practical example: theme + todos that survive reloads

// theme
const apply = (mode) => {
  document.documentElement.dataset.theme = mode;    // drives CSS variables
  store.set('theme', mode);
};
apply(store.get('theme', 'light'));

// todos
let todos = store.get('todos', []);
function addTodo(text) {
  todos = [...todos, { id: crypto.randomUUID(), text, done: false }];
  store.set('todos', todos);
  render(todos);
}

Expected output: refresh or reopen the browser — theme and todos remain exactly as left.

Privacy and safety rules

  • Never store secrets in JS-readable storage — any script (including injected third-party code) can read it; auth tokens belong in HttpOnly cookies set by the server.
  • Never store what the server should authorise — hiding a price field client-side is not security.
  • Purge on logout — localStorage outlives the session: clear user data on account switch.
  • Multi-tab? The storage event fires in other tabs — great for syncing theme across tabs.

Cookies in 30 seconds

document.cookie = 'visits=1; expires=Fri, 31 Dec 2027 23:59:59 GMT; path=/; SameSite=Lax';
console.log(document.cookie);   // only non-HttpOnly cookies visible to JS

Features like HttpOnly (invisible to JS — XSS cannot steal it), Secure, SameSite and per-cookie size limits are why servers handle auth via cookies, not localStorage. Cookies ride along with every request — that is their superpower and their cost.

The Clipboard API

await navigator.clipboard.writeText('copied!');
const text = await navigator.clipboard.readText();   // needs permission + secure context

Requires HTTPS (or localhost) and often a user gesture — exactly the pattern behind every “Copy” button (this site's code-block Copy buttons included).

History API (client-side navigation)

history.pushState({ tab: 'settings' }, '', '/settings');   // URL changes, no reload
window.addEventListener('popstate', (e) => render(e.state?.tab ?? 'home'));

This is the foundation of single-page-app routers. Note this site's module-based architecture still uses real server URLs — pushState is for apps that own their routing entirely.

Other APIs worth knowing

APIOne-liner
IntersectionObserver“Is it on screen?” — lazy images, scroll reveals (no scroll-handler jank)
ResizeObserverReact to element size changes
Geolocationnavigator.geolocation.getCurrentPosition(cb) (permission prompt)
matchMediaJS equivalent of media queries: matchMedia('(prefers-color-scheme: dark)')
navigator.onLineOnline/offline events for connection-aware UI
NotificationSystem notifications (permission required)

Common mistakes

  • Storing objects without JSON — you save [object Object] and parse garbage.
  • Un-guarded JSON.parse — one corrupted entry crashes app start; use the wrapper above.
  • Private data in localStorage — XSS reads it freely; server enforces authority anyway.
  • Storage quota ignored — QuotaExceededError after ~5 MB; catch and inform the user.
  • Sync storage reads in hot loops — getItem is synchronous I/O; cache in memory and write once (debounced).
  • Forgetting HTTPS — clipboard, service workers and many APIs are unavailable on insecure origins.

Practice checklist

  1. Persist a to-do list through reload with the store wrapper; verify with dev tools → Application → Local Storage.
  2. Build a two-tab theme switcher and sync with the storage event.
  3. Add a “Copy link” button using navigator.clipboard.writeText with a fallback message.
  4. Demonstrate quota handling: catch a simulated QuotaExceededError.

One precision tool remains for text processing — next: Regular Expressions in JavaScript.

Quiz: Browser Storage & Web APIs

5 questions · 70% to pass

Frequently Asked Questions

JavaScript is the programming language of the web. It adds behaviour to pages: responding to clicks and input, validating forms, fetching data from APIs, updating the DOM without reloading, and much more. It also runs on servers (Node.js), in desktop and mobile apps, and in build tooling.

They are unrelated languages that share a similar name for historical marketing reasons. Java is a statically-typed compiled language; JavaScript is a dynamically-typed interpreted language designed for the browser. Knowing one does not imply knowledge of the other.

Helpful but not mandatory — HTML gives you the structure JavaScript manipulates and CSS the styling it toggles. If you know basic tags, classes and selectors, you have enough to start; the DOM chapters assume only that much.

Use const by default and let when the value must change. Avoid var entirely in modern code: it is function-scoped instead of block-scoped, hoists differently, and can create accidental globals. This course uses const and let throughout.

The Document Object Model is the browser’s in-memory tree of objects representing your HTML. JavaScript reads and modifies that tree — selecting elements, changing text, adding nodes, attaching event listeners — and the browser re-renders the page to match.

== compares after converting types (so 5 == "5" is true), while === requires the same value AND the same type (5 === "5" is false). Loose equality has famously confusing rules — use === and !== everywhere unless you have a specific reason not to.

JavaScript is single-threaded, so long-running work (timers, fetches, events) is handled by the browser and its callbacks are queued. The event loop moves queued callbacks onto the call stack only when it is empty, with promise callbacks (microtasks) taking priority over timers. It is why asynchronous code never freezes the page.

A Promise is an object representing a future value — pending, fulfilled or rejected — that can be chained and combined (Promise.all). async/await is syntax sugar over promises that lets asynchronous code read top-to-bottom with ordinary try/catch error handling.

Yes. Frameworks are built on exactly these fundamentals — components are functions, state is your variable discipline, effects are events, rendering is DOM work. Developers who learn vanilla first understand what the framework is doing and debug far faster.

Type out the examples instead of copying, build small end-to-end projects (expense tracker, weather app, quiz), handle every UI state (loading, empty, success, error), and revisit fundamentals while reading real code. Consistency beats volume — a little most days outpaces occasional marathons.