🎯 What You'll Learn
- localStorage vs sessionStorage vs IndexedDB
- Building a Storage Service Layer
- TTL-based caching
- Storing files & blobs in IndexedDB
- Cross-tab communication
- Offline-first sync architecture
💡 Running Code Locally: While this online editor runs real JavaScript, some advanced examples may have limitations. For the best experience:
- Download Node.js to run JavaScript on your computer
- Use your browser's Developer Console (Press F12) to test code snippets
- Create a
.htmlfile with<script>tags and open it in your browser
Web Storage APIs
Master localStorage, sessionStorage, and IndexedDB to build fast, offline-friendly applications with persistent client-side data.
Understanding the Storage Landscape
JavaScript provides three powerful storage mechanisms, each designed for different needs:
localStorage
- • Persists forever
- • Shared across tabs
- • ~5-10 MB limit
- • Synchronous
sessionStorage
- • Deleted when tab closes
- • Isolated per tab
- • ~5-10 MB limit
- • Synchronous
IndexedDB
- • Persists forever
- • Stores objects & blobs
- • Hundreds of MB+
- • Asynchronous
localStorage: Long-Term Persistent Storage
localStorage keeps data forever (or until the user clears it manually). It's ideal for preferences, themes, cached data, and recently viewed items.
localStorage Basics
Learn how to store and retrieve data with localStorage
// localStorage keeps data forever (until manually cleared)
// Perfect for: preferences, theme, cached data, recently viewed
localStorage.setItem("theme", "dark");
const theme = localStorage.getItem("theme");
console.log("Theme:", theme); // "dark"
localStorage.removeItem("theme");
localStorage.clear(); // removes all items
// Values are ALWAYS strings - must convert objects
localStorage.setItem("user", JSON.stringify({ id: 10, role: "admin" }));
const user = JSON.parse(localStorage.getItem("
...Common Mistakes to Avoid
localStorage Common Mistakes
Avoid these common localStorage pitfalls
// ❌ COMMON MISTAKE - string concatenation
localStorage.setItem("count", 1);
console.log(localStorage.getItem("count") + 1); // "11" (string!)
// ✔ CORRECT - convert to number first
localStorage.setItem("count", 1);
console.log(Number(localStorage.getItem("count")) + 1); // 2
// ❌ Another mistake - forgetting to stringify
localStorage.setItem("data", { name: "test" }); // stores "[object Object]"
// ✔ CORRECT
localStorage.setItem("data", JSON.stringify({ name: "test" }));sessionStorage: Tab-Specific, Temporary Storage
sessionStorage is deleted when the tab closes. It's perfect for multi-step forms, temporary shopping cart state, and tab-isolated workflows.
sessionStorage Example
Learn tab-specific temporary storage
// sessionStorage is deleted when the tab closes
// Perfect for: multi-step forms, temp state, tab-specific data
sessionStorage.setItem("step", "payment");
console.log(sessionStorage.getItem("step")); // "payment"
// Each tab has SEPARATE storage!
// Open two tabs → each has its own sessionStorage
// Great for checkout flows
sessionStorage.setItem("cartDraft", JSON.stringify({
items: [{ id: 1, qty: 2 }],
step: 2
}));
// Prevents data bleed between tabs
sessionStorage.setItem("tabId", cry
...localStorage vs sessionStorage
| Feature | localStorage | sessionStorage |
|---|---|---|
| Persistence | Until cleared | Until tab closes |
| Scope | Shared across tabs | Per-tab isolation |
| Use case | Preferences & saved state | In-progress workflows |
Building a Storage Service Layer
Production apps don't store values directly into localStorage. They create a Storage Service Layer that centralizes logic, handles parsing errors, and adds features like namespacing.
Storage Service Layer
Build a production-ready storage wrapper
// Production apps use a Storage Service Layer
class StorageService {
constructor(prefix = "app") {
this.prefix = prefix;
}
key(name) {
return `${this.prefix}:${name}`;
}
get(name, fallback = null) {
try {
const value = localStorage.getItem(this.key(name));
return value ? JSON.parse(value) : fallback;
} catch {
return fallback;
}
}
set(name, value) {
localStorage.setItem(this.key(name), JSON.stringify(value));
}
remove(name) {
l
...TTL-Based Storage (Time-To-Live)
For caching API responses, you want data to expire automatically. This pattern prevents stale data while improving performance.
TTL-Based Storage
Cache data with automatic expiration
// TTL-Based Storage (Time-To-Live)
// Perfect for caching API responses
function setWithTTL(key, value, ttlMs) {
const data = {
value,
expiry: Date.now() + ttlMs
};
localStorage.setItem(key, JSON.stringify(data));
}
function getWithTTL(key) {
const item = localStorage.getItem(key);
if (!item) return null;
try {
const data = JSON.parse(item);
if (Date.now() > data.expiry) {
localStorage.removeItem(key);
return null; // expired
}
return data.va
...IndexedDB: The Browser's Real Database
IndexedDB is the production-grade solution for storing large datasets and offline application data. It supports images, audio files, documents, and complex object structures.
Opening a Database
Opening IndexedDB
Learn to create and open IndexedDB databases
// IndexedDB is async and event-based
// Perfect for: large datasets, offline apps, images, files
const request = indexedDB.open("MyAppDB", 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
// Create object stores (like tables)
if (!db.objectStoreNames.contains("notes")) {
db.createObjectStore("notes", { keyPath: "id" });
}
console.log("Database upgraded to version 1");
};
request.onsuccess = (e) => {
const db = e.target.result;
console.log("Database o
...CRUD Operations
IndexedDB CRUD
Create, read, update, delete with IndexedDB
// IndexedDB CRUD operations
// All operations must happen inside transactions
function openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open("NotesDB", 1);
request.onupgradeneeded = (e) => {
const db = e.target.result;
db.createObjectStore("notes", { keyPath: "id" });
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function addNote(note) {
const db = a
...Storing Files and Blobs
IndexedDB can store images, audio, PDFs, and binary data — essential for PWAs and offline apps.
Storing Files in IndexedDB
Store images and files for offline apps
// Storing files and blobs in IndexedDB
// Perfect for: offline image editors, file managers, PWAs
async function saveImage(id, file) {
const db = await openDB();
const tx = db.transaction("images", "readwrite");
const store = tx.objectStore("images");
store.put({
id: id,
blob: file,
name: file.name,
type: file.type,
savedAt: new Date().toISOString()
});
return new Promise((resolve, reject) => {
tx.oncomplete = () => resolve();
tx.onerror = () => re
...Checking Storage Availability
Important: Safari Private Mode often breaks IndexedDB entirely. Always build with fallbacks!
Storage Availability Check
Detect and handle storage unavailability
// Always check storage availability
// Safari Private Mode often breaks IndexedDB!
function isStorageAvailable(type) {
try {
const storage = window[type];
const test = "__storage_test__";
storage.setItem(test, "1");
storage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
// Check before using
if (isStorageAvailable("localStorage")) {
localStorage.setItem("key", "value");
} else {
console.warn("localStorage not available");
// Use in-memory fa
...Cross-Tab Communication
localStorage can act as a lightweight messaging system between tabs. Changes trigger events in other tabs automatically.
Cross-Tab Communication
Sync data between browser tabs
// Cross-Tab Communication with Storage Events
// localStorage changes trigger events in OTHER tabs
// In Tab 1: Broadcast a logout
function broadcastLogout() {
localStorage.setItem("globalLogout", Date.now().toString());
}
// In Tab 2: Listen for logout
window.addEventListener("storage", (e) => {
if (e.key === "globalLogout") {
console.log("Logout detected in another tab!");
window.location.href = "/login";
}
if (e.key === "userData") {
console.log("User data changed:",
...Offline-First Architecture
This is the architecture behind modern productivity apps like Notion, Slack, and Linear. Save locally first, sync when online.
Offline-First Sync
Build offline-capable apps with sync queues
// Offline-First Sync Architecture
// Used by Notion, Slack, Linear, etc.
class OfflineSync {
constructor() {
this.queue = [];
this.isOnline = navigator.onLine;
window.addEventListener("online", () => this.processQueue());
window.addEventListener("offline", () => this.isOnline = false);
}
async save(action) {
// 1. Save to IndexedDB immediately (instant UI)
await this.saveToIndexedDB(action.data);
// 2. Queue sync job
this.queue.push({
id
...Hybrid Storage Architecture
Professional apps combine all three storage types based on data characteristics:
Hybrid Storage Architecture
Combine all storage types in production apps
// Production Storage Architecture Example
// Dashboard app with offline support
const storageMap = {
// localStorage: small, fast settings
theme: "localStorage",
language: "localStorage",
sidebarCollapsed: "localStorage",
recentSearches: "localStorage",
// sessionStorage: tab-specific state
currentStep: "sessionStorage",
tabId: "sessionStorage",
unsavedDraft: "sessionStorage",
// IndexedDB: large data & offline
tasks: "IndexedDB",
messages: "IndexedDB",
cachedRe
...Performance Best Practices
Performance Best Practices
Optimize storage for performance
// Performance Best Practices
// ❌ BAD: Blocking the main thread on load
const config = JSON.parse(localStorage.getItem("config"));
heavyFunction(config); // UI freezes
// ✔ GOOD: Defer to idle time
requestIdleCallback(() => {
const config = JSON.parse(localStorage.getItem("config"));
heavyFunction(config);
});
// ❌ BAD: Writing on every keystroke
input.addEventListener("input", (e) => {
localStorage.setItem("draft", e.target.value);
});
// ✔ GOOD: Debounced writes
let timeout;
input.a
...Safe JSON Parsing
Always wrap JSON.parse in try-catch to prevent app crashes from corrupted data.
Safe JSON Parsing
Prevent crashes from corrupted data
// Safe JSON Parse (prevents crashes)
function safeParse(key, fallback = null) {
try {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : fallback;
} catch {
console.warn("Invalid JSON for key:", key);
localStorage.removeItem(key); // Clean up corrupted data
return fallback;
}
}
// Usage
const user = safeParse("user", { name: "Guest" });
const settings = safeParse("settings", {});
const items = safeParse("cartItems", []);
// Safe storage with
...Common Mistakes Developers Make
❌ Security Mistakes
- • Storing passwords in localStorage
- • Storing JWT tokens (XSS can steal them)
- • Storing PII or banking data
❌ Performance Mistakes
- • Storing huge arrays in localStorage
- • Writing on every keystroke
- • Loading large storage synchronously
❌ Logic Mistakes
- • Forgetting to JSON.stringify objects
- • Assuming storage always exists
- • Not handling quota errors
✔ Best Practices
- • Use storage service layers
- • Check storage availability first
- • Use IndexedDB for large data
Practice Exercises
Build these projects to master Web Storage:
- A theme switcher using localStorage
- A multi-step checkout using sessionStorage
- A fully offline notes app using IndexedDB
- A custom wrapper library that unifies all three storage types
- A cache system that stores API responses with TTL
- An IndexedDB migration (version 1 → 2 → 3)
- A "Recently Viewed Items" tracker
- Cross-tab real-time sync for a shopping cart
What You Learned
- ✔ localStorage for persistent settings
- ✔ sessionStorage for tab-specific state
- ✔ IndexedDB for large datasets
- ✔ Building storage service layers
- ✔ TTL-based caching patterns
- ✔ Cross-tab communication
- ✔ Offline-first sync architecture
- ✔ Hybrid storage design
- ✔ Storing files and blobs
- ✔ Safe JSON parsing
🎉 Lesson Complete!
You now understand how to store data client-side using localStorage, sessionStorage, and IndexedDB. Next up: Building Custom APIs.
Sign up for free to track which lessons you've completed and get learning reminders.