Skip to main content
⚡ Calmops

Web Storage API: localStorage, sessionStorage, and Cookies

Introduction

Modern browsers provide several mechanisms for storing data on the client side. The Web Storage API (localStorage and sessionStorage) offers a simple key-value store, while cookies provide a more traditional mechanism with server-side access. Choosing the right storage type depends on your data’s lifetime, size, and security requirements.

sessionStorage

sessionStorage stores data for the duration of the page session. Data is cleared when the tab or browser is closed.

// Store a value
sessionStorage.setItem('username', 'alice');
sessionStorage.setItem('theme', 'dark');

// Retrieve a value
const username = sessionStorage.getItem('username');
console.log(username);  // => "alice"

// Remove a specific item
sessionStorage.removeItem('theme');

// Clear all session storage
sessionStorage.clear();

// Check length
console.log(sessionStorage.length);  // => number of items

Storing Objects

Web Storage only stores strings. Use JSON.stringify and JSON.parse for objects:

const user = { id: 1, name: 'Alice', role: 'admin' };

// Store
sessionStorage.setItem('user', JSON.stringify(user));

// Retrieve
const stored = JSON.parse(sessionStorage.getItem('user'));
console.log(stored.name);  // => "Alice"

sessionStorage Use Cases

  • Multi-step form data (lost if user closes tab — intentional)
  • Temporary UI state (expanded panels, scroll position)
  • One-time tokens or nonces
  • Shopping cart for anonymous users (session-scoped)

localStorage

localStorage persists data indefinitely — it survives tab closes, browser restarts, and system reboots. Data is only cleared by explicit code or the user clearing browser storage.

// Store a value
localStorage.setItem('theme', 'dark');
localStorage.setItem('language', 'en');

// Retrieve a value
const theme = localStorage.getItem('theme');
console.log(theme);  // => "dark"

// Remove a specific item
localStorage.removeItem('language');

// Clear all localStorage for this origin
localStorage.clear();

// Iterate all keys
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(key, localStorage.getItem(key));
}

localStorage Use Cases

  • User preferences (theme, language, font size)
  • Cached API responses (with expiry logic)
  • Draft content (auto-save)
  • Feature flags
  • Analytics data before sending to server

Implementing Expiry for localStorage

localStorage has no built-in TTL. Implement it manually:

function setWithExpiry(key, value, ttlMs) {
  const item = {
    value,
    expiry: Date.now() + ttlMs
  };
  localStorage.setItem(key, JSON.stringify(item));
}

function getWithExpiry(key) {
  const raw = localStorage.getItem(key);
  if (!raw) return null;

  const item = JSON.parse(raw);
  if (Date.now() > item.expiry) {
    localStorage.removeItem(key);
    return null;
  }
  return item.value;
}

// Store for 1 hour
setWithExpiry('cachedData', { users: [] }, 60 * 60 * 1000);

// Retrieve (returns null if expired)
const data = getWithExpiry('cachedData');

Cookies

Cookies are the oldest browser storage mechanism. Unlike Web Storage, cookies are sent to the server with every HTTP request, making them suitable for authentication tokens and server-side state.

// Set a cookie
document.cookie = 'username=alice; path=/; max-age=3600; SameSite=Lax';

// Set a secure cookie (HTTPS only)
document.cookie = 'token=abc123; path=/; Secure; HttpOnly; SameSite=Strict';

// Read all cookies (returns a single string)
console.log(document.cookie);
// => "username=alice; theme=dark"

// Parse cookies into an object
function getCookies() {
  return document.cookie.split('; ').reduce((acc, pair) => {
    const [key, value] = pair.split('=');
    acc[decodeURIComponent(key)] = decodeURIComponent(value);
    return acc;
  }, {});
}

// Delete a cookie (set expiry in the past)
document.cookie = 'username=; path=/; max-age=0';
Attribute Description
max-age=N Expires after N seconds
expires=date Expires at specific date
path=/ Available to all paths on the domain
domain=.example.com Available to subdomains
Secure Only sent over HTTPS
HttpOnly Not accessible via JavaScript (XSS protection)
SameSite=Strict Never sent cross-site
SameSite=Lax Sent on top-level navigation
SameSite=None Always sent (requires Secure)
  • Authentication tokens (with HttpOnly and Secure)
  • Session identifiers
  • CSRF tokens
  • Cross-subdomain state sharing
  • Server-side readable preferences

IndexedDB: For Large Data

For storing large amounts of structured data (files, blobs, complex objects), use IndexedDB:

const request = indexedDB.open('MyDatabase', 1);

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  db.createObjectStore('users', { keyPath: 'id' });
};

request.onsuccess = (event) => {
  const db = event.target.result;

  // Add a record
  const tx = db.transaction('users', 'readwrite');
  tx.objectStore('users').add({ id: 1, name: 'Alice', email: '[email protected]' });

  // Read a record
  const readTx = db.transaction('users', 'readonly');
  const getReq = readTx.objectStore('users').get(1);
  getReq.onsuccess = () => console.log(getReq.result);
};

Comparison Table

Feature localStorage sessionStorage Cookie IndexedDB
Capacity ~5-10 MB ~5-10 MB ~4 KB Hundreds of MB
Persistence Permanent Tab session Configurable Permanent
Sent to server No No Yes (every request) No
Accessible via JS Yes Yes Yes (unless HttpOnly) Yes
Structured data No (strings only) No (strings only) No (strings only) Yes
Expiry control Manual Automatic Yes (max-age) Manual
Synchronous API Yes Yes Yes No (async)

Security Considerations

Never Store Sensitive Data in localStorage

localStorage is accessible to any JavaScript on the page. An XSS attack can steal everything in it:

// BAD: storing auth tokens in localStorage
localStorage.setItem('authToken', 'eyJhbGci...');

// GOOD: use HttpOnly cookies for auth tokens
// The server sets: Set-Cookie: token=...; HttpOnly; Secure; SameSite=Strict
// JavaScript cannot read HttpOnly cookies

Sanitize Before Storing

// Don't store raw user input without sanitization
function safeStore(key, value) {
  if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean') {
    value = JSON.stringify(value);
  }
  localStorage.setItem(key, String(value));
}

Storage Events

localStorage fires a storage event in other tabs when data changes — useful for cross-tab communication:

window.addEventListener('storage', (event) => {
  console.log('Key changed:', event.key);
  console.log('Old value:', event.oldValue);
  console.log('New value:', event.newValue);

  if (event.key === 'logout') {
    // Another tab logged out — redirect this tab too
    window.location.href = '/login';
  }
});

// Trigger logout across all tabs
localStorage.setItem('logout', Date.now().toString());

Practical: A Simple Storage Wrapper

const storage = {
  set(key, value, ttlMs = null) {
    const item = { value, expiry: ttlMs ? Date.now() + ttlMs : null };
    localStorage.setItem(key, JSON.stringify(item));
  },

  get(key) {
    const raw = localStorage.getItem(key);
    if (!raw) return null;
    const { value, expiry } = JSON.parse(raw);
    if (expiry && Date.now() > expiry) {
      localStorage.removeItem(key);
      return null;
    }
    return value;
  },

  remove(key) {
    localStorage.removeItem(key);
  },

  clear() {
    localStorage.clear();
  }
};

// Usage
storage.set('user', { name: 'Alice' });
storage.set('cache', data, 5 * 60 * 1000);  // 5 min TTL
const user = storage.get('user');

Resources

Comments