Back to Blog
BrowserStorageJavaScriptWeb

Browser Storage: localStorage, sessionStorage, and IndexedDB

Choose the right browser storage. From cookies to localStorage to IndexedDB with use cases explained.

B
Bootspring Team
Engineering
April 15, 2022
6 min read

Browser storage options serve different needs. Here's how to choose and use each effectively.

Storage Comparison#

| Feature | Cookies | localStorage | sessionStorage | IndexedDB | |----------------|--------------|--------------|----------------|--------------| | Capacity | 4KB | 5-10MB | 5-10MB | 50MB+ | | Persistence | Configurable | Permanent | Tab session | Permanent | | Sent to server | Yes | No | No | No | | Sync/Async | Sync | Sync | Sync | Async | | Data type | String | String | String | Any |

localStorage#

1// Simple key-value storage 2localStorage.setItem('theme', 'dark'); 3const theme = localStorage.getItem('theme'); 4localStorage.removeItem('theme'); 5localStorage.clear(); 6 7// Store objects (must serialize) 8const user = { id: '123', name: 'John' }; 9localStorage.setItem('user', JSON.stringify(user)); 10const stored = JSON.parse(localStorage.getItem('user') || 'null'); 11 12// Type-safe wrapper 13class LocalStorage<T> { 14 constructor(private key: string) {} 15 16 get(): T | null { 17 const item = localStorage.getItem(this.key); 18 return item ? JSON.parse(item) : null; 19 } 20 21 set(value: T): void { 22 localStorage.setItem(this.key, JSON.stringify(value)); 23 } 24 25 remove(): void { 26 localStorage.removeItem(this.key); 27 } 28} 29 30// Usage 31const userStorage = new LocalStorage<User>('user'); 32userStorage.set({ id: '123', name: 'John' }); 33const user = userStorage.get(); 34 35// Listen for changes (cross-tab) 36window.addEventListener('storage', (event) => { 37 if (event.key === 'user') { 38 console.log('User changed:', event.newValue); 39 } 40});

sessionStorage#

1// Same API as localStorage, but per-tab 2sessionStorage.setItem('formDraft', JSON.stringify(formData)); 3const draft = JSON.parse(sessionStorage.getItem('formDraft') || '{}'); 4 5// Good for: 6// - Form data preservation during session 7// - Temporary state 8// - Tab-specific data 9 10// React hook for sessionStorage 11function useSessionStorage<T>(key: string, initialValue: T) { 12 const [value, setValue] = useState<T>(() => { 13 const stored = sessionStorage.getItem(key); 14 return stored ? JSON.parse(stored) : initialValue; 15 }); 16 17 useEffect(() => { 18 sessionStorage.setItem(key, JSON.stringify(value)); 19 }, [key, value]); 20 21 return [value, setValue] as const; 22} 23 24// Usage 25function CheckoutForm() { 26 const [formData, setFormData] = useSessionStorage('checkout', { 27 name: '', 28 address: '', 29 }); 30 31 // Form preserved if user refreshes 32}

IndexedDB#

1// Async database for large/complex data 2class Database { 3 private db: IDBDatabase | null = null; 4 private dbName: string; 5 private version: number; 6 7 constructor(dbName: string, version: number = 1) { 8 this.dbName = dbName; 9 this.version = version; 10 } 11 12 async open(): Promise<void> { 13 return new Promise((resolve, reject) => { 14 const request = indexedDB.open(this.dbName, this.version); 15 16 request.onerror = () => reject(request.error); 17 request.onsuccess = () => { 18 this.db = request.result; 19 resolve(); 20 }; 21 22 request.onupgradeneeded = (event) => { 23 const db = (event.target as IDBOpenDBRequest).result; 24 25 // Create object stores 26 if (!db.objectStoreNames.contains('users')) { 27 db.createObjectStore('users', { keyPath: 'id' }); 28 } 29 30 if (!db.objectStoreNames.contains('posts')) { 31 const store = db.createObjectStore('posts', { keyPath: 'id' }); 32 store.createIndex('userId', 'userId', { unique: false }); 33 store.createIndex('createdAt', 'createdAt', { unique: false }); 34 } 35 }; 36 }); 37 } 38 39 async add<T>(storeName: string, data: T): Promise<void> { 40 return new Promise((resolve, reject) => { 41 const transaction = this.db!.transaction(storeName, 'readwrite'); 42 const store = transaction.objectStore(storeName); 43 const request = store.add(data); 44 45 request.onerror = () => reject(request.error); 46 request.onsuccess = () => resolve(); 47 }); 48 } 49 50 async get<T>(storeName: string, key: string): Promise<T | undefined> { 51 return new Promise((resolve, reject) => { 52 const transaction = this.db!.transaction(storeName, 'readonly'); 53 const store = transaction.objectStore(storeName); 54 const request = store.get(key); 55 56 request.onerror = () => reject(request.error); 57 request.onsuccess = () => resolve(request.result); 58 }); 59 } 60 61 async getAll<T>(storeName: string): Promise<T[]> { 62 return new Promise((resolve, reject) => { 63 const transaction = this.db!.transaction(storeName, 'readonly'); 64 const store = transaction.objectStore(storeName); 65 const request = store.getAll(); 66 67 request.onerror = () => reject(request.error); 68 request.onsuccess = () => resolve(request.result); 69 }); 70 } 71 72 async put<T>(storeName: string, data: T): Promise<void> { 73 return new Promise((resolve, reject) => { 74 const transaction = this.db!.transaction(storeName, 'readwrite'); 75 const store = transaction.objectStore(storeName); 76 const request = store.put(data); 77 78 request.onerror = () => reject(request.error); 79 request.onsuccess = () => resolve(); 80 }); 81 } 82 83 async delete(storeName: string, key: string): Promise<void> { 84 return new Promise((resolve, reject) => { 85 const transaction = this.db!.transaction(storeName, 'readwrite'); 86 const store = transaction.objectStore(storeName); 87 const request = store.delete(key); 88 89 request.onerror = () => reject(request.error); 90 request.onsuccess = () => resolve(); 91 }); 92 } 93} 94 95// Usage 96const db = new Database('myApp', 1); 97await db.open(); 98 99await db.add('users', { id: '123', name: 'John', email: 'john@example.com' }); 100const user = await db.get('users', '123'); 101const allUsers = await db.getAll('users');

Cookies#

1// Set cookie 2document.cookie = 'theme=dark; path=/; max-age=31536000; secure; samesite=strict'; 3 4// Parse cookies 5function getCookie(name: string): string | null { 6 const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`)); 7 return match ? decodeURIComponent(match[2]) : null; 8} 9 10// Set cookie helper 11function setCookie( 12 name: string, 13 value: string, 14 options: { 15 maxAge?: number; 16 expires?: Date; 17 path?: string; 18 domain?: string; 19 secure?: boolean; 20 sameSite?: 'strict' | 'lax' | 'none'; 21 } = {} 22): void { 23 let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`; 24 25 if (options.maxAge) cookie += `; max-age=${options.maxAge}`; 26 if (options.expires) cookie += `; expires=${options.expires.toUTCString()}`; 27 if (options.path) cookie += `; path=${options.path}`; 28 if (options.domain) cookie += `; domain=${options.domain}`; 29 if (options.secure) cookie += '; secure'; 30 if (options.sameSite) cookie += `; samesite=${options.sameSite}`; 31 32 document.cookie = cookie; 33} 34 35// Delete cookie 36function deleteCookie(name: string): void { 37 document.cookie = `${name}=; max-age=0; path=/`; 38} 39 40// Usage 41setCookie('session', 'abc123', { 42 maxAge: 86400, 43 path: '/', 44 secure: true, 45 sameSite: 'strict', 46});

When to Use What#

1// localStorage: Persistent user preferences 2localStorage.setItem('theme', 'dark'); 3localStorage.setItem('language', 'en'); 4 5// sessionStorage: Temporary form data 6sessionStorage.setItem('checkoutStep', '2'); 7sessionStorage.setItem('cartItems', JSON.stringify(items)); 8 9// IndexedDB: Large datasets, offline data 10await db.add('articles', { id: '1', content: '...', images: [...] }); 11 12// Cookies: Auth tokens, server-readable data 13setCookie('auth_token', token, { secure: true, sameSite: 'strict' });

Storage with Expiration#

1interface StorageItem<T> { 2 value: T; 3 expiry: number; 4} 5 6class ExpiringStorage<T> { 7 constructor(private key: string) {} 8 9 set(value: T, ttlMs: number): void { 10 const item: StorageItem<T> = { 11 value, 12 expiry: Date.now() + ttlMs, 13 }; 14 localStorage.setItem(this.key, JSON.stringify(item)); 15 } 16 17 get(): T | null { 18 const itemStr = localStorage.getItem(this.key); 19 if (!itemStr) return null; 20 21 const item: StorageItem<T> = JSON.parse(itemStr); 22 23 if (Date.now() > item.expiry) { 24 localStorage.removeItem(this.key); 25 return null; 26 } 27 28 return item.value; 29 } 30} 31 32// Usage 33const cache = new ExpiringStorage<User[]>('cachedUsers'); 34cache.set(users, 60 * 60 * 1000); // 1 hour 35const cached = cache.get(); // null if expired

Best Practices#

Security: ✓ Never store sensitive data unencrypted ✓ Use httpOnly cookies for auth tokens ✓ Set appropriate cookie flags ✓ Validate stored data Performance: ✓ Don't store too much in localStorage ✓ Use IndexedDB for large data ✓ Batch IndexedDB operations ✓ Clean up expired data Reliability: ✓ Handle storage quota errors ✓ Provide fallbacks ✓ Validate JSON parsing ✓ Test private browsing mode

Conclusion#

Choose storage based on data size, persistence needs, and whether the server needs access. localStorage for small persistent data, sessionStorage for tab-specific temporary data, IndexedDB for large datasets, and cookies when the server needs the data.

Share this article

Help spread the word about Bootspring