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

  1. 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;
      }
    }
    
  2. Check storage availability

    function isStorageAvailable() {
      try {
        const test = '__test__';
        localStorage.setItem(test, test);
        localStorage.removeItem(test);
        return true;
      } catch (e) {
        return false;
      }
    }
    
  3. 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!