Docs
Module-18-Browser-APIs-and-Storage
11.1 localStorage and sessionStorage
π Table of Contents
- β’Web Storage Overview
- β’localStorage vs sessionStorage
- β’Basic Operations
- β’Storing Complex Data
- β’Storage Events
- β’Storage Limits
- β’Best Practices
Web Storage Overview
Web Storage provides mechanisms for storing key-value pairs in the browser.
Storage Types
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser Storage Options β
βββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββββ€
β β β
β βββββββββββββββ β βββββββββββββββ βββββββββββββββ β
β β localStorageβ β βsessionStorageβ β Cookies β β
β ββββββββ¬βββββββ β ββββββββ¬βββββββ ββββββββ¬βββββββ β
β β β β β β
β Persistent β Tab-only Sent to server β
β ~5-10 MB β ~5-10 MB ~4 KB limit β
β No expiry β Until tab close Has expiry β
β β β
βββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββββ
When to Use Each
| Use Case | Storage Type |
|---|---|
| User preferences | localStorage |
| Shopping cart (persistent) | localStorage |
| Form draft (temporary) | sessionStorage |
| One-time messages | sessionStorage |
| Multi-tab sync needed | localStorage |
| Sensitive session data | sessionStorage |
| Auth tokens (simple apps) | localStorage/sessionStorage |
| Tracking (cross-site) | Cookies |
localStorage vs sessionStorage
Comparison Table
| Feature | localStorage | sessionStorage |
|---|---|---|
| Persistence | Until cleared | Until tab closes |
| Scope | Origin-wide | Per tab/window |
| Capacity | ~5-10 MB | ~5-10 MB |
| Shared across tabs | β Yes | β No |
| Survives refresh | β Yes | β Yes |
| Survives browser restart | β Yes | β No |
| Sent to server | β No | β No |
Visual Comparison
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BROWSER β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β localStorage β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β Shared across ALL tabs for same origin β β β
β β β example.com/page1 ββ example.com/page2 β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β ββββββββββββββββββββ ββββββββββββββββββββ β
β β Tab 1 β β Tab 2 β β
β β ββββββββββββββββ β β ββββββββββββββββ β β
β β βsessionStorageβ β β βsessionStorageβ β β Separate! β
β β β (isolated) β β β β (isolated) β β β
β β ββββββββββββββββ β β ββββββββββββββββ β β
β ββββββββββββββββββββ ββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Lifecycle
localStorage Lifecycle:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΊ
Page Load Page Refresh Browser Close Days Later
β β β β
βΌ βΌ βΌ βΌ
βββββββ βββββββ βββββββ βββββββ
βData β βData β βData β βData β
βEXISTSβ βEXISTSβ βEXISTSβ βEXISTSβ
βββββββ βββββββ βββββββ βββββββ
sessionStorage Lifecycle:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΊ
Page Load Page Refresh Tab/Window Close
β β β
βΌ βΌ βΌ
βββββββ βββββββ βββββββ
βData β βData β βData β
βEXISTSβ βEXISTSβ β GONE β
βββββββ βββββββ βββββββ
Basic Operations
Both localStorage and sessionStorage share the same API:
Core Methods
// Store a value
localStorage.setItem('key', 'value');
sessionStorage.setItem('key', 'value');
// Retrieve a value
const value = localStorage.getItem('key');
const value2 = sessionStorage.getItem('key');
// Remove a specific item
localStorage.removeItem('key');
sessionStorage.removeItem('key');
// Clear all items
localStorage.clear();
sessionStorage.clear();
// Get number of items
const count = localStorage.length;
// Get key by index
const key = localStorage.key(0);
Methods Reference
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Storage Methods β
βββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββββββ€
β Method β Description β
βββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββββββ€
β setItem(key, value) β Store value (converts to string) β
β getItem(key) β Retrieve value (returns null if none) β
β removeItem(key) β Delete specific key β
β clear() β Remove ALL items β
β key(index) β Get key name by index β
β length β Number of stored items (property) β
βββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββββββ
Alternative Syntax
// Dot notation (works but not recommended)
localStorage.username = 'john';
const user = localStorage.username;
// Bracket notation
localStorage['username'] = 'john';
const user = localStorage['username'];
// Why use setItem/getItem instead?
// 1. Clearer intent
// 2. getItem returns null (not undefined) for missing keys
// 3. Avoids conflicts with built-in properties
// 4. Works with keys that aren't valid identifiers
Checking for Existence
// Check if key exists
if (localStorage.getItem('theme') !== null) {
console.log('Theme is set');
}
// Using 'in' operator (works but less reliable)
if ('theme' in localStorage) {
console.log('Theme exists');
}
// Check storage availability
function isStorageAvailable(type) {
try {
const storage = window[type];
const test = '__storage_test__';
storage.setItem(test, test);
storage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
if (isStorageAvailable('localStorage')) {
// Safe to use localStorage
}
Storing Complex Data
Storage only accepts strings! Non-strings are converted automatically.
The Problem
// Numbers become strings
localStorage.setItem('count', 42);
console.log(localStorage.getItem('count')); // "42" (string!)
console.log(typeof localStorage.getItem('count')); // "string"
// Booleans become strings
localStorage.setItem('active', true);
console.log(localStorage.getItem('active')); // "true" (string!)
console.log(localStorage.getItem('active') === true); // false!
// Objects become "[object Object]"
localStorage.setItem('user', { name: 'John' });
console.log(localStorage.getItem('user')); // "[object Object]" π±
The Solution: JSON
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Storing Complex Data β
β β
β JavaScript Object localStorage β
β βββββββββββββββββββ βββββββββββββββββββ β
β β { β stringify() β β β
β β name: "John", β ββββββββββββββββΊβ '{"name":"John",β β
β β age: 30 β β "age":30}' β β
β β } β β β β
β βββββββββββββββββββ βββββββββββββββββββ β
β β β
β βββββββββββββββββββ β β
β β { β parse() β β
β β name: "John", β ββββββββββββββββββββββββββ β
β β age: 30 β β
β β } β β
β βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Storing Objects and Arrays
// Storing an object
const user = {
name: 'John',
email: 'john@example.com',
preferences: {
theme: 'dark',
language: 'en',
},
};
localStorage.setItem('user', JSON.stringify(user));
// Retrieving an object
const savedUser = JSON.parse(localStorage.getItem('user'));
console.log(savedUser.name); // "John"
console.log(savedUser.preferences.theme); // "dark"
// Storing an array
const todos = [
{ id: 1, text: 'Learn JavaScript', done: true },
{ id: 2, text: 'Learn Web Storage', done: false },
];
localStorage.setItem('todos', JSON.stringify(todos));
// Retrieving an array
const savedTodos = JSON.parse(localStorage.getItem('todos'));
savedTodos.forEach((todo) => console.log(todo.text));
Helper Functions
// Generic storage helpers
const storage = {
set(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
get(key, defaultValue = null) {
const item = localStorage.getItem(key);
if (item === null) return defaultValue;
try {
return JSON.parse(item);
} catch (e) {
return item; // Return as string if not valid JSON
}
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
},
};
// Usage
storage.set('user', { name: 'John', age: 30 });
const user = storage.get('user');
const missing = storage.get('nonexistent', { default: true });
Handling Dates
// Dates need special handling (JSON converts to string)
const event = {
name: 'Meeting',
date: new Date('2024-12-15'),
};
localStorage.setItem('event', JSON.stringify(event));
const saved = JSON.parse(localStorage.getItem('event'));
console.log(saved.date); // "2024-12-15T00:00:00.000Z" (string!)
console.log(new Date(saved.date)); // Date object again
// Use a reviver function for automatic conversion
function dateReviver(key, value) {
// ISO 8601 date pattern
const datePattern = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
if (typeof value === 'string' && datePattern.test(value)) {
return new Date(value);
}
return value;
}
const restored = JSON.parse(localStorage.getItem('event'), dateReviver);
console.log(restored.date instanceof Date); // true
Storage Events
The storage event fires when storage changes in another tab/window.
Event Properties
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β StorageEvent Properties β
ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ€
β Property β Description β
ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββ€
β key β The key that changed (null if clear()) β
β oldValue β Previous value (null if new key) β
β newValue β New value (null if removed) β
β url β URL of page that made the change β
β storageArea β The storage object (localStorage/session) β
ββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββ
Listening for Changes
// Listen for storage changes from OTHER tabs
window.addEventListener('storage', function (e) {
console.log('Storage changed!');
console.log('Key:', e.key);
console.log('Old value:', e.oldValue);
console.log('New value:', e.newValue);
console.log('URL:', e.url);
// React to specific changes
if (e.key === 'theme') {
applyTheme(e.newValue);
}
if (e.key === 'user' && e.newValue === null) {
// User logged out in another tab
redirectToLogin();
}
});
Cross-Tab Communication
Tab 1 Tab 2
ββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ
β β β β
β localStorage.setItem( β β window.addEventListener( β
β 'message', β βββββΊ β 'storage', handler β
β 'Hello from Tab 1' β β ); β
β ); β β β
β β β // handler receives event β
β β β // with key='message' β
β β β // newValue='Hello...' β
ββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ
Note: Tab 1 does NOT receive the storage event!
Only OTHER tabs/windows do.
Same-Tab Updates
// For same-tab updates, create your own event system
class StorageManager {
constructor() {
this.listeners = new Map();
}
set(key, value) {
const oldValue = localStorage.getItem(key);
const newValue = JSON.stringify(value);
localStorage.setItem(key, newValue);
// Notify local listeners
this._notify(key, oldValue, newValue);
}
get(key) {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : null;
}
onChange(key, callback) {
if (!this.listeners.has(key)) {
this.listeners.set(key, []);
}
this.listeners.get(key).push(callback);
}
_notify(key, oldValue, newValue) {
const callbacks = this.listeners.get(key) || [];
callbacks.forEach((cb) =>
cb({
key,
oldValue,
newValue,
})
);
}
}
const store = new StorageManager();
store.onChange('count', (e) => console.log('Count changed:', e.newValue));
store.set('count', 42); // Logs: Count changed: 42
Storage Limits
Size Limits
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Storage Limits by Browser β
ββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββ€
β Browser β localStorage + sessionStorage Limit β
ββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββ€
β Chrome β ~5 MB per origin β
β Firefox β ~5 MB per origin β
β Safari β ~5 MB per origin β
β Edge β ~5 MB per origin β
β Mobile Safari β ~5 MB per origin β
ββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββ
Note: "per origin" means protocol + domain + port
https://example.com and http://example.com are different!
Checking Available Space
// Estimate storage usage (modern browsers)
if (navigator.storage && navigator.storage.estimate) {
navigator.storage.estimate().then((estimate) => {
console.log('Usage:', estimate.usage);
console.log('Quota:', estimate.quota);
console.log(
'Percent used:',
((estimate.usage / estimate.quota) * 100).toFixed(2) + '%'
);
});
}
// Calculate current localStorage usage
function getLocalStorageSize() {
let total = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
// Each character is 2 bytes in JavaScript (UTF-16)
total += (key.length + localStorage[key].length) * 2;
}
}
return total;
}
console.log('Current usage:', getLocalStorageSize(), 'bytes');
console.log('Current usage:', (getLocalStorageSize() / 1024).toFixed(2), 'KB');
Handling Quota Exceeded
function safeSetItem(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
if (
e.name === 'QuotaExceededError' ||
e.name === 'NS_ERROR_DOM_QUOTA_REACHED'
) {
console.error('Storage quota exceeded');
// Possible recovery strategies:
// 1. Clear old data
// 2. Compress data
// 3. Use IndexedDB instead
// 4. Alert user
return false;
}
throw e; // Re-throw other errors
}
}
// Usage
if (!safeSetItem('largeData', hugeObject)) {
// Handle storage full scenario
cleanupOldData();
}
Best Practices
1. Use Namespaced Keys
// Bad - risk of conflicts
localStorage.setItem('user', data);
localStorage.setItem('theme', 'dark');
// Good - namespace your keys
const APP_PREFIX = 'myApp_';
localStorage.setItem(`${APP_PREFIX}user`, data);
localStorage.setItem(`${APP_PREFIX}theme`, 'dark');
// Or use an object structure
const appData = {
user: { name: 'John' },
theme: 'dark',
settings: {
/* ... */
},
};
localStorage.setItem('myApp', JSON.stringify(appData));
2. Add Version Control
const STORAGE_VERSION = '1.2';
function saveData(data) {
const wrapper = {
version: STORAGE_VERSION,
timestamp: Date.now(),
data: data,
};
localStorage.setItem('myApp', JSON.stringify(wrapper));
}
function loadData() {
const saved = localStorage.getItem('myApp');
if (!saved) return null;
const wrapper = JSON.parse(saved);
if (wrapper.version !== STORAGE_VERSION) {
// Handle migration or clear old data
console.log('Storage version mismatch, migrating...');
return migrateData(wrapper);
}
return wrapper.data;
}
3. Handle Errors Gracefully
class SafeStorage {
static isAvailable() {
try {
const test = '__test__';
localStorage.setItem(test, test);
localStorage.removeItem(test);
return true;
} catch (e) {
return false;
}
}
static set(key, value) {
if (!this.isAvailable()) {
console.warn('Storage not available');
return false;
}
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
console.error('Storage error:', e);
return false;
}
}
static get(key, defaultValue = null) {
if (!this.isAvailable()) {
return defaultValue;
}
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (e) {
console.error('Storage read error:', e);
return defaultValue;
}
}
}
4. Don't Store Sensitive Data
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β οΈ Security Warning β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β NEVER store in localStorage/sessionStorage: β
β β
β β Passwords (plain or hashed) β
β β Credit card numbers β
β β Social security numbers β
β β API keys with write access β
β β Private encryption keys β
β β
β Why? β
β - XSS attacks can read all storage β
β - Browser extensions can access storage β
β - No encryption by default β
β - Persists even after logout (localStorage) β
β β
β For auth tokens: Consider httpOnly cookies instead β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
5. Clean Up Old Data
// Add expiration to stored data
function setWithExpiry(key, value, ttlMs) {
const item = {
value: value,
expiry: Date.now() + ttlMs,
};
localStorage.setItem(key, JSON.stringify(item));
}
function getWithExpiry(key) {
const itemStr = localStorage.getItem(key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
if (Date.now() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
}
// Set item that expires in 1 hour
setWithExpiry('tempData', { foo: 'bar' }, 60 * 60 * 1000);
Quick Reference
Cheat Sheet
// Basic operations
localStorage.setItem('key', 'value'); // Store
localStorage.getItem('key'); // Retrieve (null if missing)
localStorage.removeItem('key'); // Delete
localStorage.clear(); // Clear all
localStorage.length; // Count
localStorage.key(0); // Get key by index
// Store objects/arrays
localStorage.setItem('obj', JSON.stringify({ a: 1 }));
const obj = JSON.parse(localStorage.getItem('obj'));
// Session storage (same API)
sessionStorage.setItem('key', 'value');
// Storage event (cross-tab only)
window.addEventListener('storage', (e) => {
console.log(e.key, e.oldValue, e.newValue);
});
Summary
| Concept | Key Points |
|---|---|
| localStorage | Persists forever, shared across tabs |
| sessionStorage | Cleared when tab closes, isolated per tab |
| API Methods | setItem, getItem, removeItem, clear, key, length |
| Complex Data | Use JSON.stringify/parse for objects/arrays |
| Storage Event | Fires in OTHER tabs when storage changes |
| Limits | ~5 MB per origin |
| Security | Don't store sensitive data, accessible to XSS |
| Best Practices | Namespace keys, version data, handle errors |