LocalStorage and SessionStorage in JavaScript
Introduction
LocalStorage and SessionStorage are Web Storage APIs that allow you to store data on the client side. They provide a simple key-value storage mechanism that persists across page reloads (localStorage) or for the duration of the session (sessionStorage). Understanding these APIs is essential for building applications that need to remember user preferences and data.
Understanding Web Storage
LocalStorage vs SessionStorage
// Both have the same API, but different lifespans
// LocalStorage: Persists until explicitly deleted
localStorage.setItem('theme', 'dark');
console.log(localStorage.getItem('theme')); // 'dark' (persists after page reload)
// SessionStorage: Cleared when tab/window closes
sessionStorage.setItem('tempData', 'value');
console.log(sessionStorage.getItem('tempData')); // 'value' (cleared on close)
Storage Limits
// Typical storage limits:
// - LocalStorage: 5-10MB per domain
// - SessionStorage: 5-10MB per tab
// Check available storage
function getStorageSize() {
let size = 0;
for (let key in localStorage) {
if (localStorage.hasOwnProperty(key)) {
size += localStorage[key].length + key.length;
}
}
return (size / 1024).toFixed(2) + ' KB';
}
console.log(getStorageSize());
LocalStorage Methods
Setting and Getting Items
// Set item
localStorage.setItem('username', 'john_doe');
localStorage.setItem('theme', 'dark');
// Get item
const username = localStorage.getItem('username');
console.log(username); // 'john_doe'
// Get non-existent item
const missing = localStorage.getItem('nonexistent');
console.log(missing); // null
// Check if key exists
if (localStorage.getItem('theme')) {
console.log('Theme preference found');
}
Removing Items
// Remove specific item
localStorage.setItem('tempData', 'value');
localStorage.removeItem('tempData');
console.log(localStorage.getItem('tempData')); // null
// Clear all items
localStorage.setItem('key1', 'value1');
localStorage.setItem('key2', 'value2');
localStorage.clear();
console.log(localStorage.length); // 0
Iterating Over Items
// Get all keys
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = localStorage.getItem(key);
console.log(`${key}: ${value}`);
}
// Using Object.keys (modern approach)
Object.keys(localStorage).forEach(key => {
console.log(`${key}: ${localStorage.getItem(key)}`);
});
// Get all items as object
function getAllLocalStorage() {
const items = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
items[key] = localStorage.getItem(key);
}
return items;
}
console.log(getAllLocalStorage());
Storing Complex Data
JSON Serialization
// Store objects as JSON
const user = {
name: 'Alice',
email: '[email protected]',
preferences: {
theme: 'dark',
language: 'en'
}
};
// Store
localStorage.setItem('user', JSON.stringify(user));
// Retrieve
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.name); // 'Alice'
console.log(storedUser.preferences.theme); // 'dark'
Safe JSON Parsing
// Safe JSON parsing with error handling
function safeGetJSON(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (error) {
console.error(`Error parsing ${key}:`, error);
return defaultValue;
}
}
function safeSetJSON(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (error) {
console.error(`Error storing ${key}:`, error);
return false;
}
}
// Usage
safeSetJSON('config', { theme: 'dark', fontSize: 14 });
const config = safeGetJSON('config', {});
console.log(config);
Storage Events
Listening to Storage Changes
// Listen for storage changes (from other tabs/windows)
window.addEventListener('storage', (event) => {
console.log('Storage changed');
console.log('Key:', event.key);
console.log('Old value:', event.oldValue);
console.log('New value:', event.newValue);
console.log('URL:', event.url);
});
// Practical example: Sync across tabs
window.addEventListener('storage', (event) => {
if (event.key === 'theme') {
applyTheme(event.newValue);
}
});
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
}
Practical Patterns
Pattern 1: User Preferences
// Store and retrieve user preferences
class UserPreferences {
static set(key, value) {
const prefs = this.getAll();
prefs[key] = value;
localStorage.setItem('userPreferences', JSON.stringify(prefs));
}
static get(key, defaultValue = null) {
const prefs = this.getAll();
return prefs[key] !== undefined ? prefs[key] : defaultValue;
}
static getAll() {
try {
const prefs = localStorage.getItem('userPreferences');
return prefs ? JSON.parse(prefs) : {};
} catch (error) {
return {};
}
}
static remove(key) {
const prefs = this.getAll();
delete prefs[key];
localStorage.setItem('userPreferences', JSON.stringify(prefs));
}
static clear() {
localStorage.removeItem('userPreferences');
}
}
// Usage
UserPreferences.set('theme', 'dark');
UserPreferences.set('language', 'en');
console.log(UserPreferences.get('theme')); // 'dark'
console.log(UserPreferences.getAll()); // { theme: 'dark', language: 'en' }
Pattern 2: Form Auto-Save
// Auto-save form data
class FormAutoSave {
constructor(formId, storageKey) {
this.form = document.getElementById(formId);
this.storageKey = storageKey;
this.init();
}
init() {
// Restore saved data
this.restore();
// Save on input
this.form.addEventListener('input', () => this.save());
// Clear on submit
this.form.addEventListener('submit', () => this.clear());
}
save() {
const formData = new FormData(this.form);
const data = Object.fromEntries(formData);
localStorage.setItem(this.storageKey, JSON.stringify(data));
}
restore() {
try {
const data = JSON.parse(localStorage.getItem(this.storageKey));
if (data) {
Object.entries(data).forEach(([key, value]) => {
const field = this.form.elements[key];
if (field) {
field.value = value;
}
});
}
} catch (error) {
console.error('Error restoring form:', error);
}
}
clear() {
localStorage.removeItem(this.storageKey);
}
}
// Usage
const autoSave = new FormAutoSave('myForm', 'formData');
Pattern 3: Cache Manager
// Simple cache with expiration
class CacheManager {
static set(key, value, expirationMinutes = null) {
const item = {
value,
timestamp: Date.now(),
expiration: expirationMinutes ? Date.now() + (expirationMinutes * 60 * 1000) : null
};
localStorage.setItem(key, JSON.stringify(item));
}
static get(key) {
try {
const item = JSON.parse(localStorage.getItem(key));
if (!item) return null;
// Check expiration
if (item.expiration && Date.now() > item.expiration) {
localStorage.removeItem(key);
return null;
}
return item.value;
} catch (error) {
return null;
}
}
static remove(key) {
localStorage.removeItem(key);
}
static clear() {
localStorage.clear();
}
}
// Usage
CacheManager.set('apiData', { users: [...] }, 30); // Expires in 30 minutes
const data = CacheManager.get('apiData');
Pattern 4: Session Manager
// Manage user sessions
class SessionManager {
static createSession(userId, sessionData) {
const session = {
userId,
sessionId: Math.random().toString(36).substr(2, 9),
createdAt: Date.now(),
...sessionData
};
sessionStorage.setItem('session', JSON.stringify(session));
return session;
}
static getSession() {
try {
return JSON.parse(sessionStorage.getItem('session'));
} catch (error) {
return null;
}
}
static isSessionValid() {
const session = this.getSession();
return session !== null;
}
static updateSession(updates) {
const session = this.getSession();
if (session) {
const updated = { ...session, ...updates };
sessionStorage.setItem('session', JSON.stringify(updated));
}
}
static destroySession() {
sessionStorage.removeItem('session');
}
}
// Usage
SessionManager.createSession('user123', { role: 'admin' });
console.log(SessionManager.isSessionValid()); // true
SessionManager.destroySession();
Real-World Examples
Example 1: Theme Switcher with Persistence
// Theme switcher that remembers preference
class ThemeSwitcher {
constructor() {
this.themes = ['light', 'dark', 'auto'];
this.currentTheme = this.loadTheme();
this.applyTheme();
this.setupListeners();
}
loadTheme() {
const saved = localStorage.getItem('theme');
return saved || 'light';
}
saveTheme(theme) {
if (this.themes.includes(theme)) {
localStorage.setItem('theme', theme);
this.currentTheme = theme;
this.applyTheme();
}
}
applyTheme() {
document.documentElement.setAttribute('data-theme', this.currentTheme);
}
toggleTheme() {
const index = this.themes.indexOf(this.currentTheme);
const nextTheme = this.themes[(index + 1) % this.themes.length];
this.saveTheme(nextTheme);
}
setupListeners() {
window.addEventListener('storage', (event) => {
if (event.key === 'theme') {
this.currentTheme = event.newValue;
this.applyTheme();
}
});
}
}
// Usage
const themeSwitcher = new ThemeSwitcher();
document.getElementById('themeToggle').addEventListener('click', () => {
themeSwitcher.toggleTheme();
});
Example 2: Shopping Cart Persistence
// Shopping cart that persists across sessions
class ShoppingCart {
constructor() {
this.storageKey = 'shoppingCart';
this.items = this.loadCart();
}
loadCart() {
try {
const cart = localStorage.getItem(this.storageKey);
return cart ? JSON.parse(cart) : [];
} catch (error) {
return [];
}
}
saveCart() {
localStorage.setItem(this.storageKey, JSON.stringify(this.items));
}
addItem(product) {
const existing = this.items.find(item => item.id === product.id);
if (existing) {
existing.quantity += product.quantity || 1;
} else {
this.items.push({ ...product, quantity: product.quantity || 1 });
}
this.saveCart();
}
removeItem(productId) {
this.items = this.items.filter(item => item.id !== productId);
this.saveCart();
}
getTotal() {
return this.items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
}
clear() {
this.items = [];
localStorage.removeItem(this.storageKey);
}
}
// Usage
const cart = new ShoppingCart();
cart.addItem({ id: 1, name: 'Laptop', price: 999.99, quantity: 1 });
console.log(cart.getTotal()); // 999.99
Example 3: Draft Recovery
// Auto-save drafts and recover
class DraftManager {
constructor(editorId, draftKey) {
this.editor = document.getElementById(editorId);
this.draftKey = draftKey;
this.autoSaveInterval = 30000; // 30 seconds
this.init();
}
init() {
this.recoverDraft();
this.startAutoSave();
// Clear draft on successful save
window.addEventListener('contentSaved', () => {
this.clearDraft();
});
}
startAutoSave() {
setInterval(() => {
this.saveDraft();
}, this.autoSaveInterval);
}
saveDraft() {
const draft = {
content: this.editor.value,
timestamp: Date.now()
};
localStorage.setItem(this.draftKey, JSON.stringify(draft));
}
recoverDraft() {
try {
const draft = JSON.parse(localStorage.getItem(this.draftKey));
if (draft) {
this.editor.value = draft.content;
console.log('Draft recovered from', new Date(draft.timestamp));
}
} catch (error) {
console.error('Error recovering draft:', error);
}
}
clearDraft() {
localStorage.removeItem(this.draftKey);
}
}
// Usage
const draftManager = new DraftManager('editor', 'articleDraft');
Common Mistakes to Avoid
Mistake 1: Storing Large Objects Without Limits
// โ Wrong - Can exceed storage limit
const largeData = new Array(1000000).fill({ data: 'value' });
localStorage.setItem('data', JSON.stringify(largeData));
// โ
Correct - Check size before storing
function canStore(key, value) {
try {
const serialized = JSON.stringify(value);
const sizeInKB = new Blob([serialized]).size / 1024;
return sizeInKB < 5000; // 5MB limit
} catch (error) {
return false;
}
}
if (canStore('data', largeData)) {
localStorage.setItem('data', JSON.stringify(largeData));
}
Mistake 2: Not Handling Storage Errors
// โ Wrong - No error handling
localStorage.setItem('key', value);
// โ
Correct - Handle errors
function safeSetItem(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (error) {
if (error.name === 'QuotaExceededError') {
console.error('Storage quota exceeded');
} else {
console.error('Storage error:', error);
}
return false;
}
}
Mistake 3: Storing Sensitive Data
// โ Wrong - Never store sensitive data
localStorage.setItem('password', userPassword);
localStorage.setItem('creditCard', cardNumber);
// โ
Correct - Use secure methods for sensitive data
// Use httpOnly cookies for authentication tokens
// Use secure backend storage for sensitive information
Mistake 4: Not Clearing Old Data
// โ Wrong - Storage fills up with old data
for (let i = 0; i < 1000; i++) {
localStorage.setItem(`item_${i}`, data);
}
// โ
Correct - Implement cleanup
function cleanupOldData(maxAge = 7 * 24 * 60 * 60 * 1000) {
const now = Date.now();
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const item = JSON.parse(localStorage.getItem(key));
if (item.timestamp && now - item.timestamp > maxAge) {
localStorage.removeItem(key);
}
}
}
Summary
Web Storage APIs provide client-side persistence:
- LocalStorage: Persists until explicitly deleted
- SessionStorage: Cleared when tab/window closes
- Both have ~5-10MB storage limit
- Use JSON for complex data
- Handle errors and storage limits
- Never store sensitive data
- Implement cleanup for old data
- Use storage events for cross-tab communication
- Create wrapper classes for common patterns
Related Resources
- MDN: Web Storage API
- MDN: localStorage
- MDN: sessionStorage
- MDN: Storage Events
- IndexedDB for Large Data
Next Steps
Continue your learning journey:
- Cookies: Creation, Reading, Deletion
- Fetch API: Making HTTP Requests
- XMLHttpRequest and AJAX
- Geolocation API
Comments