Docs For AI
Browser & Network

Client Storage

localStorage, sessionStorage, IndexedDB, cookies, and caching strategies

Client Storage

Browsers provide various storage mechanisms for persisting data. Each has different use cases, capacities, and security implications.

Storage Comparison

StorageCapacityPersistenceScopeAccess
Cookies~4KBConfigurableServer + ClientHTTP requests
localStorage~5-10MBPermanentOriginClient only
sessionStorage~5-10MBSession (tab)Origin + TabClient only
IndexedDBLarge (GBs)PermanentOriginClient only
Cache APILargePermanentOriginService Worker

Web Storage API

localStorage

// Store data (synchronous)
localStorage.setItem('user', JSON.stringify({ id: 1, name: 'John' }));

// Retrieve data
const user = JSON.parse(localStorage.getItem('user') || 'null');

// Remove item
localStorage.removeItem('user');

// Clear all
localStorage.clear();

// Check storage availability
function isLocalStorageAvailable(): boolean {
  try {
    const test = '__storage_test__';
    localStorage.setItem(test, test);
    localStorage.removeItem(test);
    return true;
  } catch {
    return false;
  }
}

// Type-safe wrapper
class TypedStorage<T> {
  constructor(private key: string, private defaultValue: T) {}

  get(): T {
    try {
      const item = localStorage.getItem(this.key);
      return item ? JSON.parse(item) : this.defaultValue;
    } catch {
      return this.defaultValue;
    }
  }

  set(value: T): void {
    localStorage.setItem(this.key, JSON.stringify(value));
  }

  remove(): void {
    localStorage.removeItem(this.key);
  }
}

// Usage
const themeStorage = new TypedStorage<'light' | 'dark'>('theme', 'light');
themeStorage.set('dark');
const theme = themeStorage.get(); // 'dark'

sessionStorage

// Same API as localStorage, but cleared when tab closes
sessionStorage.setItem('tempData', JSON.stringify(data));

// Useful for:
// - Form data backup during navigation
// - Single-session state
// - Tab-specific data

Storage Events

// Listen for storage changes from other tabs
window.addEventListener('storage', (event) => {
  if (event.key === 'user') {
    console.log('User data changed in another tab');
    console.log('Old value:', event.oldValue);
    console.log('New value:', event.newValue);
    console.log('URL:', event.url);
  }
});

// Broadcast to other tabs
function broadcastLogout() {
  localStorage.setItem('logout', Date.now().toString());
  localStorage.removeItem('logout');
}

IndexedDB

IndexedDB is a low-level API for storing large amounts of structured data.

Basic Usage

// Open database
function openDatabase(): Promise<IDBDatabase> {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('MyDatabase', 1);

    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve(request.result);

    request.onupgradeneeded = (event) => {
      const db = (event.target as IDBOpenDBRequest).result;

      // Create object store
      if (!db.objectStoreNames.contains('users')) {
        const store = db.createObjectStore('users', { keyPath: 'id' });
        store.createIndex('email', 'email', { unique: true });
        store.createIndex('name', 'name', { unique: false });
      }
    };
  });
}

// CRUD operations
async function addUser(user: User): Promise<void> {
  const db = await openDatabase();
  return new Promise((resolve, reject) => {
    const transaction = db.transaction(['users'], 'readwrite');
    const store = transaction.objectStore('users');
    const request = store.add(user);

    request.onsuccess = () => resolve();
    request.onerror = () => reject(request.error);
  });
}

async function getUser(id: number): Promise<User | undefined> {
  const db = await openDatabase();
  return new Promise((resolve, reject) => {
    const transaction = db.transaction(['users'], 'readonly');
    const store = transaction.objectStore('users');
    const request = store.get(id);

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

async function getAllUsers(): Promise<User[]> {
  const db = await openDatabase();
  return new Promise((resolve, reject) => {
    const transaction = db.transaction(['users'], 'readonly');
    const store = transaction.objectStore('users');
    const request = store.getAll();

    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

async function deleteUser(id: number): Promise<void> {
  const db = await openDatabase();
  return new Promise((resolve, reject) => {
    const transaction = db.transaction(['users'], 'readwrite');
    const store = transaction.objectStore('users');
    const request = store.delete(id);

    request.onsuccess = () => resolve();
    request.onerror = () => reject(request.error);
  });
}
import Dexie, { Table } from 'dexie';

interface User {
  id?: number;
  name: string;
  email: string;
  createdAt: Date;
}

class AppDatabase extends Dexie {
  users!: Table<User>;

  constructor() {
    super('AppDatabase');
    this.version(1).stores({
      users: '++id, email, name, createdAt',
    });
  }
}

const db = new AppDatabase();

// CRUD operations
async function createUser(user: Omit<User, 'id'>) {
  return db.users.add({ ...user, createdAt: new Date() });
}

async function getUserByEmail(email: string) {
  return db.users.where('email').equals(email).first();
}

async function searchUsers(query: string) {
  return db.users
    .where('name')
    .startsWithIgnoreCase(query)
    .toArray();
}

async function updateUser(id: number, changes: Partial<User>) {
  return db.users.update(id, changes);
}

async function deleteUser(id: number) {
  return db.users.delete(id);
}

// Transactions
async function transferData(fromId: number, toId: number) {
  await db.transaction('rw', db.users, async () => {
    const from = await db.users.get(fromId);
    const to = await db.users.get(toId);
    // Atomic operations...
  });
}

Cookies

// Read cookies
function getCookie(name: string): string | null {
  const match = document.cookie.match(new RegExp(`(^| )${name}=([^;]+)`));
  return match ? decodeURIComponent(match[2]) : null;
}

// Set cookie
function setCookie(
  name: string,
  value: string,
  options: {
    expires?: Date | number;  // Date or days
    path?: string;
    domain?: string;
    secure?: boolean;
    sameSite?: 'Strict' | 'Lax' | 'None';
  } = {}
): void {
  let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;

  if (options.expires) {
    const date = options.expires instanceof Date
      ? options.expires
      : new Date(Date.now() + options.expires * 864e5);
    cookie += `; expires=${date.toUTCString()}`;
  }

  if (options.path) cookie += `; path=${options.path}`;
  if (options.domain) cookie += `; domain=${options.domain}`;
  if (options.secure) cookie += '; secure';
  if (options.sameSite) cookie += `; samesite=${options.sameSite}`;

  document.cookie = cookie;
}

// Delete cookie
function deleteCookie(name: string, path = '/'): void {
  document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}`;
}

// Usage
setCookie('preferences', JSON.stringify({ theme: 'dark' }), {
  expires: 365,
  path: '/',
  secure: true,
  sameSite: 'Strict',
});
# Secure cookie settings (set by server)
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict; Path=/; Max-Age=86400

# HttpOnly prevents JavaScript access (XSS protection)
# Secure ensures HTTPS only
# SameSite prevents CSRF

Cache API

// Cache API (for Service Workers and offline)
const CACHE_NAME = 'app-v1';

// Cache resources
async function cacheResources(urls: string[]): Promise<void> {
  const cache = await caches.open(CACHE_NAME);
  await cache.addAll(urls);
}

// Get from cache with network fallback
async function cacheFirst(request: Request): Promise<Response> {
  const cached = await caches.match(request);
  if (cached) return cached;

  const response = await fetch(request);
  const cache = await caches.open(CACHE_NAME);
  cache.put(request, response.clone());
  return response;
}

// Network first with cache fallback
async function networkFirst(request: Request): Promise<Response> {
  try {
    const response = await fetch(request);
    const cache = await caches.open(CACHE_NAME);
    cache.put(request, response.clone());
    return response;
  } catch {
    const cached = await caches.match(request);
    if (cached) return cached;
    throw new Error('No cached response available');
  }
}

// Stale while revalidate
async function staleWhileRevalidate(request: Request): Promise<Response> {
  const cache = await caches.open(CACHE_NAME);
  const cached = await cache.match(request);

  const fetchPromise = fetch(request).then((response) => {
    cache.put(request, response.clone());
    return response;
  });

  return cached || fetchPromise;
}

// Clear old caches
async function clearOldCaches(): Promise<void> {
  const keys = await caches.keys();
  await Promise.all(
    keys
      .filter((key) => key !== CACHE_NAME)
      .map((key) => caches.delete(key))
  );
}

Storage Quota

// Check storage quota
async function checkStorageQuota() {
  if (navigator.storage && navigator.storage.estimate) {
    const { usage, quota } = await navigator.storage.estimate();
    const usedMB = (usage || 0) / (1024 * 1024);
    const quotaMB = (quota || 0) / (1024 * 1024);
    console.log(`Using ${usedMB.toFixed(2)} MB of ${quotaMB.toFixed(2)} MB`);
    return { usage, quota };
  }
}

// Request persistent storage
async function requestPersistentStorage() {
  if (navigator.storage && navigator.storage.persist) {
    const persistent = await navigator.storage.persist();
    console.log(`Persistent storage: ${persistent ? 'granted' : 'denied'}`);
    return persistent;
  }
  return false;
}

Best Practices

Storage Guidelines

  1. Use appropriate storage for the use case
  2. Never store sensitive data in localStorage
  3. Encrypt sensitive data if stored client-side
  4. Handle storage quota errors gracefully
  5. Use IndexedDB for large or structured data
  6. Implement data migration for schema changes
  7. Clear stale data periodically
  8. Use service workers for offline caching

Storage Decision Tree

What should I use?
├── Needs to be sent with HTTP requests?
│   └── Cookies (HttpOnly for security)
├── Large structured data?
│   └── IndexedDB (with Dexie.js)
├── Small key-value data?
│   ├── Persist across sessions?
│   │   └── localStorage
│   └── Only for this session?
│       └── sessionStorage
└── Offline/caching?
    └── Cache API (Service Worker)

On this page