Browser Storage and Web APIs · Lesson 37 of 40
localStorage, sessionStorage and Cookies
Websites remember things: your dark-mode choice, a half-filled form, the items in a guest cart, which lessons you've completed. Browsers give JavaScript several places to keep that data. Choosing the right one – and knowing what must never be stored in the browser – is part of building apps that are both convenient and secure.
In this lesson
localStorageandsessionStorage- Storing objects safely as JSON, with expiry
- Cookies and how they differ
- IndexedDB for larger data, and security rules
The options at a glance
| localStorage | sessionStorage | Cookies | IndexedDB | |
|---|---|---|---|---|
| Lifetime | until cleared | until the tab closes | until expiry date | until cleared |
| Size | ~5 MB per site | ~5 MB per tab | ~4 KB each | large (hundreds of MB+) |
| Sent to server? | no | no | yes, with every request | no |
| API | simple, synchronous | simple, synchronous | string-based | asynchronous, more complex |
| Typical use | preferences, drafts, cached data | one-tab wizard state | sessions, auth (set by the server) | offline apps, big datasets |
localStorage basics
localStorage.setItem("theme", "dark");
console.log(localStorage.getItem("theme")); // "dark"
console.log(localStorage.getItem("missing")); // null
localStorage.setItem("visits", 3); // stored as the string "3"
const visits = Number(localStorage.getItem("visits")) + 1;
localStorage.removeItem("theme");
localStorage.clear(); // removes everything for this site
Storage is per origin (protocol + domain + port): https://learnfrenzy.com can't read another site's data. sessionStorage has exactly the same methods but is cleared when the tab closes and isn't shared between tabs.
Storing objects: JSON
Storage only holds strings. Store objects with JSON.stringify and read them with JSON.parse – inside try/catch, because stored data can be missing, old or corrupted:
const cart = [{ sku: "PEN", qty: 2 }, { sku: "BAG", qty: 1 }];
localStorage.setItem("cart", JSON.stringify(cart));
function readCart() {
try {
return JSON.parse(localStorage.getItem("cart")) ?? [];
} catch {
return []; // corrupted value → start fresh
}
}
A reusable storage helper with expiry
Real apps wrap storage in a small helper: JSON handling, a key prefix, an optional expiry time, and safety when storage is full or blocked (private mode, browser settings). The helper below takes the storage object as a parameter, so here it runs with a simple in-memory stand-in; in the browser you pass localStorage.
function createStore(storage, prefix = "lf:") {
return {
set(key, value, ttlMs) {
const record = { value, expires: ttlMs ? Date.now() + ttlMs : null };
try {
storage.setItem(prefix + key, JSON.stringify(record));
return true;
} catch {
return false; // quota exceeded or storage blocked
}
},
get(key, fallback = null) {
try {
const record = JSON.parse(storage.getItem(prefix + key));
if (!record) return fallback;
if (record.expires && Date.now() > record.expires) {
storage.removeItem(prefix + key);
return fallback;
}
return record.value;
} catch {
return fallback;
}
},
};
}
// In-memory stand-in with the same API as localStorage
const memory = new Map();
const fakeStorage = {
getItem: (k) => memory.get(k) ?? null,
setItem: (k, v) => memory.set(k, String(v)),
removeItem: (k) => memory.delete(k),
};
const store = createStore(fakeStorage);
store.set("prefs", { theme: "dark", fontSize: 18 });
store.set("otp-sent", true, 50); // expires in 50 ms
fakeStorage.setItem("lf:broken", "{not json");
console.log(store.get("prefs"));
console.log(store.get("otp-sent"));
await new Promise((r) => setTimeout(r, 80));
console.log(store.get("otp-sent", false)); // expired
console.log(store.get("broken", "default")); // corrupted → fallback
console.log([...memory.keys()]);
{ theme: 'dark', fontSize: 18 }
true
false
default
[ 'lf:prefs', 'lf:broken' ]In the browser: const store = createStore(localStorage);
Syncing between tabs
// Fires in OTHER tabs of the same site when localStorage changes
window.addEventListener("storage", (event) => {
if (event.key === "lf:prefs") applyTheme(JSON.parse(event.newValue).value.theme);
});
Cookies
Cookies are small strings that the browser sends to the server with every request to that site. That makes them the right tool for server sessions – and the wrong tool for general client-side storage.
// Set a cookie from JavaScript (non-sensitive values only)
document.cookie = "lang=en; Max-Age=31536000; Path=/; SameSite=Lax; Secure";
// Read: document.cookie is one string "a=1; lang=en"
const cookies = Object.fromEntries(
document.cookie.split("; ").filter(Boolean).map((c) => c.split("=").map(decodeURIComponent))
);
console.log(cookies.lang);
// Delete: set it again with Max-Age=0
document.cookie = "lang=; Max-Age=0; Path=/";
| Cookie attribute | Meaning |
|---|---|
HttpOnly | JavaScript can't read it – protects session cookies from XSS (set by the server) |
Secure | sent over HTTPS only |
SameSite=Lax/Strict | limits sending on cross-site requests – helps against CSRF |
Max-Age / Expires | lifetime; without it the cookie ends with the browser session |
IndexedDB in one paragraph
For large or structured data – offline notes, cached API results, files – use IndexedDB: an asynchronous, transactional database in the browser. Its raw API is verbose, so most developers use a small wrapper library such as idb or Dexie.
Security: what never goes in browser storage
- Any JavaScript on your page – including a compromised third-party script – can read localStorage and sessionStorage. Never store passwords, card numbers or other secrets there.
- Prefer
HttpOnly,Secure,SameSitecookies set by the server for login sessions, rather than tokens in localStorage. - Treat stored data as untrusted input: validate it when you read it back.
Practice
Try it yourself
Save a form draft: on every
inputevent store the textarea's value in localStorage, and restore it when the page loads.Show solution
const area = document.querySelector("#message"); area.value = localStorage.getItem("draft:message") ?? ""; area.addEventListener("input", () => localStorage.setItem("draft:message", area.value)); document.querySelector("#contact-form").addEventListener("submit", () => { localStorage.removeItem("draft:message"); // clear once sent });
Quick check
What type does localStorage.getItem return?
A string, or null if the key doesn't exist.
localStorage vs sessionStorage?
localStorage persists until cleared and is shared across tabs; sessionStorage is per tab and cleared when the tab closes.
Why shouldn't login tokens go in localStorage?
Any script on the page, including injected XSS code, can read it; HttpOnly cookies can't be read by JavaScript.
Summary
- localStorage persists, sessionStorage lasts one tab; both store strings per origin.
- Store objects as JSON, read with try/catch, add expiry in a small helper.
- Cookies travel with every request – use server-set HttpOnly cookies for sessions; never store secrets in the browser.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.