DOM & BrowserFeatured
JavaScript Local Storage and Session Storage: Complete Guide
Master web storage in JavaScript. Learn localStorage, sessionStorage, cookies, and IndexedDB for client-side data persistence.
By JavaScriptDoc Team•
localStoragesessionStorageweb storagecookiesbrowser
JavaScript Local Storage and Session Storage: Complete Guide
Web Storage provides a way to store data in the browser, allowing web applications to save information that persists across page refreshes and browser sessions.
Understanding Web Storage
Web Storage consists of two mechanisms:
- localStorage: Data persists even after closing the browser
- sessionStorage: Data persists only for the duration of the page session
// Check if storage is available
function storageAvailable(type) {
try {
const storage = window[type];
const x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
} catch (e) {
return false;
}
}
if (storageAvailable('localStorage')) {
console.log('localStorage is available');
}
if (storageAvailable('sessionStorage')) {
console.log('sessionStorage is available');
}
localStorage Basics
Setting and Getting Data
// Setting items
localStorage.setItem('username', 'JohnDoe');
localStorage.setItem('theme', 'dark');
localStorage.setItem('language', 'en');
// Getting items
const username = localStorage.getItem('username');
console.log(username); // 'JohnDoe'
// Non-existent keys return null
const nonExistent = localStorage.getItem('nonExistent');
console.log(nonExistent); // null
// Alternative syntax (not recommended)
localStorage.username = 'JaneDoe';
localStorage['theme'] = 'light';
// Reading with alternative syntax
console.log(localStorage.username); // 'JaneDoe'
console.log(localStorage['theme']); // 'light'
Storing Complex Data
// localStorage only stores strings
// Storing objects and arrays requires JSON
// Storing objects
const user = {
id: 1,
name: 'John Doe',
email: 'john@example.com',
preferences: {
theme: 'dark',
notifications: true,
},
};
localStorage.setItem('user', JSON.stringify(user));
// Retrieving objects
const storedUser = JSON.parse(localStorage.getItem('user'));
console.log(storedUser.name); // 'John Doe'
// Storing arrays
const todos = [
{ id: 1, text: 'Learn JavaScript', done: true },
{ id: 2, text: 'Build a project', done: false },
];
localStorage.setItem('todos', JSON.stringify(todos));
// Retrieving arrays
const storedTodos = JSON.parse(localStorage.getItem('todos') || '[]');
// Helper functions for JSON operations
const storage = {
get(key, defaultValue = null) {
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : defaultValue;
} catch (e) {
console.error('Error parsing stored data:', e);
return defaultValue;
}
},
set(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
return true;
} catch (e) {
console.error('Error storing data:', e);
return false;
}
},
remove(key) {
localStorage.removeItem(key);
},
clear() {
localStorage.clear();
},
};
Removing and Clearing Data
// Remove specific item
localStorage.removeItem('username');
// Check if item exists
if (localStorage.getItem('theme') !== null) {
console.log('Theme is set');
}
// Clear all localStorage data
localStorage.clear();
// Get number of items
console.log(localStorage.length);
// Iterate through 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
Object.keys(localStorage).forEach((key) => {
console.log(`${key}: ${localStorage.getItem(key)}`);
});
sessionStorage
sessionStorage works exactly like localStorage but with different persistence:
// sessionStorage data expires when tab is closed
sessionStorage.setItem('tempData', 'This will be gone when tab closes');
sessionStorage.setItem('sessionId', 'abc123');
// Same API as localStorage
const tempData = sessionStorage.getItem('tempData');
sessionStorage.removeItem('tempData');
sessionStorage.clear();
// Use case: Form data preservation
function saveFormData() {
const formData = {
name: document.getElementById('name').value,
email: document.getElementById('email').value,
message: document.getElementById('message').value,
};
sessionStorage.setItem('formDraft', JSON.stringify(formData));
}
function restoreFormData() {
const saved = sessionStorage.getItem('formDraft');
if (saved) {
const formData = JSON.parse(saved);
document.getElementById('name').value = formData.name || '';
document.getElementById('email').value = formData.email || '';
document.getElementById('message').value = formData.message || '';
}
}
// Auto-save form data
document.querySelectorAll('input, textarea').forEach((element) => {
element.addEventListener('input', saveFormData);
});
// Restore on page load
window.addEventListener('DOMContentLoaded', restoreFormData);
Storage Events
Storage events allow communication between tabs/windows:
// Listen for storage changes from other tabs
window.addEventListener('storage', (event) => {
console.log('Storage changed:', {
key: event.key,
oldValue: event.oldValue,
newValue: event.newValue,
url: event.url,
storageArea: event.storageArea,
});
// React to specific changes
if (event.key === 'theme') {
updateTheme(event.newValue);
}
});
// Cross-tab communication
class TabCommunicator {
constructor() {
this.callbacks = new Map();
window.addEventListener('storage', (event) => {
if (event.key && this.callbacks.has(event.key)) {
const callback = this.callbacks.get(event.key);
callback(event.newValue, event.oldValue);
}
});
}
on(key, callback) {
this.callbacks.set(key, callback);
}
emit(key, data) {
localStorage.setItem(
key,
JSON.stringify({
data,
timestamp: Date.now(),
tabId: this.getTabId(),
})
);
}
getTabId() {
if (!sessionStorage.getItem('tabId')) {
sessionStorage.setItem('tabId', Math.random().toString(36).substr(2));
}
return sessionStorage.getItem('tabId');
}
}
// Usage
const communicator = new TabCommunicator();
communicator.on('user-action', (newValue) => {
const { data, tabId } = JSON.parse(newValue);
console.log(`Tab ${tabId} performed action:`, data);
});
communicator.emit('user-action', { type: 'login', user: 'john' });
Practical Examples
User Preferences Manager
class PreferencesManager {
constructor() {
this.defaults = {
theme: 'light',
language: 'en',
fontSize: 'medium',
notifications: true,
autoSave: true,
};
this.preferences = this.load();
}
load() {
const stored = localStorage.getItem('preferences');
if (stored) {
try {
return { ...this.defaults, ...JSON.parse(stored) };
} catch (e) {
console.error('Failed to load preferences:', e);
}
}
return { ...this.defaults };
}
save() {
try {
localStorage.setItem('preferences', JSON.stringify(this.preferences));
this.notifyChange();
return true;
} catch (e) {
console.error('Failed to save preferences:', e);
return false;
}
}
get(key) {
return this.preferences[key] ?? this.defaults[key];
}
set(key, value) {
if (key in this.defaults) {
this.preferences[key] = value;
this.save();
}
}
reset() {
this.preferences = { ...this.defaults };
this.save();
}
notifyChange() {
window.dispatchEvent(
new CustomEvent('preferencesChanged', {
detail: this.preferences,
})
);
}
}
// Usage
const prefs = new PreferencesManager();
// Get preference
console.log(prefs.get('theme')); // 'light'
// Set preference
prefs.set('theme', 'dark');
// Listen for changes
window.addEventListener('preferencesChanged', (event) => {
applyTheme(event.detail.theme);
updateLanguage(event.detail.language);
});
Shopping Cart Storage
class CartStorage {
constructor() {
this.storageKey = 'shopping_cart';
this.expiryKey = 'cart_expiry';
this.expiryTime = 7 * 24 * 60 * 60 * 1000; // 7 days
}
getCart() {
this.checkExpiry();
const cart = localStorage.getItem(this.storageKey);
return cart ? JSON.parse(cart) : { items: [], total: 0 };
}
saveCart(cart) {
localStorage.setItem(this.storageKey, JSON.stringify(cart));
localStorage.setItem(this.expiryKey, Date.now() + this.expiryTime);
}
addItem(product) {
const cart = this.getCart();
const existingItem = cart.items.find((item) => item.id === product.id);
if (existingItem) {
existingItem.quantity += 1;
} else {
cart.items.push({
...product,
quantity: 1,
});
}
this.updateTotal(cart);
this.saveCart(cart);
return cart;
}
removeItem(productId) {
const cart = this.getCart();
cart.items = cart.items.filter((item) => item.id !== productId);
this.updateTotal(cart);
this.saveCart(cart);
return cart;
}
updateQuantity(productId, quantity) {
const cart = this.getCart();
const item = cart.items.find((item) => item.id === productId);
if (item) {
if (quantity <= 0) {
return this.removeItem(productId);
}
item.quantity = quantity;
this.updateTotal(cart);
this.saveCart(cart);
}
return cart;
}
updateTotal(cart) {
cart.total = cart.items.reduce((sum, item) => {
return sum + item.price * item.quantity;
}, 0);
}
clearCart() {
localStorage.removeItem(this.storageKey);
localStorage.removeItem(this.expiryKey);
}
checkExpiry() {
const expiry = localStorage.getItem(this.expiryKey);
if (expiry && Date.now() > parseInt(expiry)) {
this.clearCart();
}
}
}
Authentication Token Manager
class AuthTokenManager {
constructor() {
this.accessTokenKey = 'access_token';
this.refreshTokenKey = 'refresh_token';
this.userKey = 'user_data';
}
setTokens(accessToken, refreshToken) {
localStorage.setItem(this.accessTokenKey, accessToken);
localStorage.setItem(this.refreshTokenKey, refreshToken);
}
getAccessToken() {
return localStorage.getItem(this.accessTokenKey);
}
getRefreshToken() {
return localStorage.getItem(this.refreshTokenKey);
}
setUser(userData) {
localStorage.setItem(this.userKey, JSON.stringify(userData));
}
getUser() {
const userData = localStorage.getItem(this.userKey);
return userData ? JSON.parse(userData) : null;
}
isAuthenticated() {
return !!this.getAccessToken();
}
clearAuth() {
localStorage.removeItem(this.accessTokenKey);
localStorage.removeItem(this.refreshTokenKey);
localStorage.removeItem(this.userKey);
}
// Auto-attach token to requests
attachToRequests() {
const token = this.getAccessToken();
if (token) {
// Axios
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// Fetch
const originalFetch = window.fetch;
window.fetch = (url, options = {}) => {
options.headers = {
...options.headers,
Authorization: `Bearer ${token}`,
};
return originalFetch(url, options);
};
}
}
}
Storage Limitations and Quotas
// Check storage quota (Chrome/Edge)
if ('storage' in navigator && 'estimate' in navigator.storage) {
navigator.storage.estimate().then(({ usage, quota }) => {
console.log(`Using ${usage} out of ${quota} bytes.`);
const percentUsed = ((usage / quota) * 100).toFixed(2);
console.log(`Storage used: ${percentUsed}%`);
});
}
// Handle quota exceeded errors
function safeSetItem(key, value) {
try {
localStorage.setItem(key, value);
return true;
} catch (e) {
if (e.name === 'QuotaExceededError') {
console.error('Storage quota exceeded');
// Try to free up space
cleanupOldData();
// Retry once
try {
localStorage.setItem(key, value);
return true;
} catch (retryError) {
return false;
}
}
throw e;
}
}
// Storage cleanup strategy
function cleanupOldData() {
const keysToCheck = [
{ key: 'cache_', maxAge: 24 * 60 * 60 * 1000 }, // 1 day
{ key: 'temp_', maxAge: 60 * 60 * 1000 }, // 1 hour
];
Object.keys(localStorage).forEach((key) => {
keysToCheck.forEach(({ key: prefix, maxAge }) => {
if (key.startsWith(prefix)) {
try {
const data = JSON.parse(localStorage.getItem(key));
if (data.timestamp && Date.now() - data.timestamp > maxAge) {
localStorage.removeItem(key);
}
} catch (e) {
// If parsing fails, remove the item
localStorage.removeItem(key);
}
}
});
});
}
Cookies vs Web Storage
// Cookie utilities
const CookieManager = {
set(name, value, days = 7) {
const expires = new Date();
expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000);
document.cookie = `${name}=${encodeURIComponent(value)};expires=${expires.toUTCString()};path=/`;
},
get(name) {
const nameEQ = name + '=';
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i].trim();
if (c.indexOf(nameEQ) === 0) {
return decodeURIComponent(c.substring(nameEQ.length));
}
}
return null;
},
delete(name) {
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/`;
},
};
// When to use what:
// Cookies: Authentication tokens, server-side needed data
// localStorage: User preferences, application state
// sessionStorage: Temporary data, form drafts
// Hybrid approach
class HybridStorage {
constructor() {
this.cookieKeys = ['auth_token', 'session_id'];
this.localKeys = ['preferences', 'cache'];
this.sessionKeys = ['form_data', 'temp_state'];
}
set(key, value, options = {}) {
if (this.cookieKeys.includes(key)) {
CookieManager.set(key, value, options.days || 7);
} else if (this.sessionKeys.includes(key)) {
sessionStorage.setItem(key, JSON.stringify(value));
} else {
localStorage.setItem(key, JSON.stringify(value));
}
}
get(key) {
if (this.cookieKeys.includes(key)) {
return CookieManager.get(key);
} else if (this.sessionKeys.includes(key)) {
const value = sessionStorage.getItem(key);
return value ? JSON.parse(value) : null;
} else {
const value = localStorage.getItem(key);
return value ? JSON.parse(value) : null;
}
}
}
IndexedDB for Large Data
// IndexedDB wrapper for large storage needs
class DatabaseStorage {
constructor(dbName = 'AppDatabase', version = 1) {
this.dbName = dbName;
this.version = version;
this.db = null;
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this.db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains('data')) {
db.createObjectStore('data', { keyPath: 'id' });
}
};
});
}
async set(id, data) {
const transaction = this.db.transaction(['data'], 'readwrite');
const store = transaction.objectStore('data');
return new Promise((resolve, reject) => {
const request = store.put({ id, data, timestamp: Date.now() });
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async get(id) {
const transaction = this.db.transaction(['data'], 'readonly');
const store = transaction.objectStore('data');
return new Promise((resolve, reject) => {
const request = store.get(id);
request.onsuccess = () => {
const result = request.result;
resolve(result ? result.data : null);
};
request.onerror = () => reject(request.error);
});
}
async delete(id) {
const transaction = this.db.transaction(['data'], 'readwrite');
const store = transaction.objectStore('data');
return new Promise((resolve, reject) => {
const request = store.delete(id);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
// Usage
async function useLargeStorage() {
const db = new DatabaseStorage();
await db.init();
// Store large data
const largeData = new ArrayBuffer(10 * 1024 * 1024); // 10MB
await db.set('large-file', largeData);
// Retrieve
const retrieved = await db.get('large-file');
console.log('Retrieved:', retrieved);
}
Security Considerations
// Encryption wrapper for sensitive data
class SecureStorage {
constructor(encryptionKey) {
this.key = encryptionKey;
}
// Simple XOR encryption (use proper encryption in production)
encrypt(text) {
return text
.split('')
.map((char, i) => {
return String.fromCharCode(
char.charCodeAt(0) ^ this.key.charCodeAt(i % this.key.length)
);
})
.join('');
}
decrypt(encrypted) {
return this.encrypt(encrypted); // XOR is reversible
}
setItem(key, value) {
const encrypted = this.encrypt(JSON.stringify(value));
localStorage.setItem(key, encrypted);
}
getItem(key) {
const encrypted = localStorage.getItem(key);
if (!encrypted) return null;
try {
const decrypted = this.decrypt(encrypted);
return JSON.parse(decrypted);
} catch (e) {
console.error('Failed to decrypt:', e);
return null;
}
}
}
// Best practices for sensitive data
class SafeStorage {
constructor() {
// Never store sensitive data in plain text
this.sensitiveKeys = ['password', 'ssn', 'credit_card'];
}
set(key, value) {
if (this.sensitiveKeys.some((k) => key.includes(k))) {
console.warn(`Attempting to store potentially sensitive data: ${key}`);
return false;
}
localStorage.setItem(key, JSON.stringify(value));
return true;
}
// Add expiration to stored data
setWithExpiry(key, value, ttl) {
const now = new Date();
const item = {
value: value,
expiry: now.getTime() + ttl,
};
localStorage.setItem(key, JSON.stringify(item));
}
getWithExpiry(key) {
const itemStr = localStorage.getItem(key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
const now = new Date();
if (now.getTime() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
}
}
Best Practices
-
Always handle JSON parsing errors
function getSafeItem(key, defaultValue = null) { try { const item = localStorage.getItem(key); return item ? JSON.parse(item) : defaultValue; } catch (e) { return defaultValue; } }
-
Check storage availability
function isStorageAvailable() { try { const test = '__test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch (e) { return false; } }
-
Implement storage limits
class LimitedStorage { constructor(maxItems = 100) { this.maxItems = maxItems; } set(key, value) { if (localStorage.length >= this.maxItems) { this.removeOldest(); } localStorage.setItem( key, JSON.stringify({ value, timestamp: Date.now(), }) ); } removeOldest() { let oldest = null; let oldestTime = Infinity; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); try { const item = JSON.parse(localStorage.getItem(key)); if (item.timestamp < oldestTime) { oldest = key; oldestTime = item.timestamp; } } catch (e) { // Remove corrupted data localStorage.removeItem(key); return; } } if (oldest) { localStorage.removeItem(oldest); } } }
Conclusion
Web Storage provides powerful client-side storage capabilities:
- localStorage for persistent data
- sessionStorage for temporary data
- Storage events for cross-tab communication
- IndexedDB for large data sets
- Security considerations for sensitive data
Key takeaways:
- Always stringify objects before storing
- Handle quota exceeded errors gracefully
- Use appropriate storage for different data types
- Consider data expiration strategies
- Never store sensitive data unencrypted
- Test for storage availability
Master web storage to build offline-capable, performant web applications!