Skip to main content

Command Palette

Search for a command to run...

Browser Storage: LocalStorage SessionStorage IndexedDB

Learn: Browser Storage: LocalStorage SessionStorage IndexedDB

Updated
6 min readView as Markdown
T

Welcome to TopperBlog! 👋

I'm a tech content creator passionate about helping developers level up their careers and master cutting-edge technologies.

🎯 What I Write About: • AI/ML Engineering & LLMs • Web3 & Blockchain Development
• System Design & Architecture • Interview Preparation (FAANG) • Freelancing & Remote Work • Modern Tech Stacks (Next.js, React, Rust, TypeScript) • Performance Optimization & Best Practices

💼 Mission: Sharing practical, actionable insights that accelerate your tech career and maximize your earning potential.

📚 15+ In-Depth Guides covering everything from earning $10k/month as a freelancer to cracking FAANG interviews.

🌐 Let's connect and grow together in this amazing tech journey!

#TechBlogger #SoftwareEngineering #CareerGrowth #WebDevelopment #AIEngineering

Browser Storage: LocalStorage, SessionStorage, IndexedDB

LocalStorage

Problem

You need to persist user data across browser sessions without server involvement, such as user preferences, theme settings, or form data that should survive page refreshes and browser restarts.

Solution

LocalStorage provides synchronous, persistent key-value storage with no expiration. Data remains until explicitly deleted or the browser cache is cleared.

Code

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

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

// Check if key exists
if (localStorage.getItem('theme')) {
  console.log('Theme preference found');
}

// Remove specific item
localStorage.removeItem('theme');

// Clear all storage
localStorage.clear();

// Get all keys
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  console.log(`${key}: ${localStorage.getItem(key)}`);
}

// Listen for storage changes (from other tabs)
window.addEventListener('storage', (event) => {
  if (event.key === 'theme') {
    console.log(`Theme changed from ${event.oldValue} to ${event.newValue}`);
  }
});

Tips

  • Limit: ~5-10MB per domain (varies by browser)
  • Synchronous: Blocks execution; avoid storing large objects
  • No expiration: Manually manage cleanup
  • Cross-tab communication: Storage events fire in other tabs, not current one
  • Security: Never store sensitive data (passwords, tokens); vulnerable to XSS
  • Best for: User preferences, theme settings, non-sensitive user data

SessionStorage

Problem

You need temporary storage that persists only during the current browser tab session, useful for multi-step forms, temporary UI state, or data that shouldn't survive a page close.

Solution

SessionStorage works identically to LocalStorage but clears when the tab closes. Each tab has its own isolated storage.

Code

// Store form step data
sessionStorage.setItem('formStep', '2');
sessionStorage.setItem('formData', JSON.stringify({
  name: 'Alice',
  email: 'alice@example.com'
}));

// Retrieve during same session
const step = sessionStorage.getItem('formStep');
const data = JSON.parse(sessionStorage.getItem('formData'));

// Update multi-step form
function saveFormStep(stepNumber, data) {
  sessionStorage.setItem(`step_${stepNumber}`, JSON.stringify(data));
}

function getFormStep(stepNumber) {
  return JSON.parse(sessionStorage.getItem(`step_${stepNumber}`));
}

// Clear on form completion
function completeForm() {
  sessionStorage.removeItem('formStep');
  sessionStorage.removeItem('formData');
}

// Detect session end
window.addEventListener('beforeunload', () => {
  console.log('Session ending, data will be cleared');
});

Tips

  • Isolation: Each tab/window has separate storage; no cross-tab access
  • Auto-cleanup: Automatically cleared when tab closes
  • Same limits: ~5-10MB per domain
  • Synchronous: Same performance considerations as LocalStorage
  • Best for: Multi-step wizards, temporary UI state, session-specific data, draft content

IndexedDB

Problem

You need to store large amounts of structured data (megabytes to gigabytes), perform complex queries, create indexes, or work with objects without serialization. Examples: offline-first apps, caching API responses, managing large datasets.

Solution

IndexedDB is an asynchronous, NoSQL database in the browser supporting transactions, indexes, and complex queries. It's the most powerful client-side storage option.

Code

// Open/create database
const dbRequest = indexedDB.open('myAppDB', 1);

dbRequest.onerror = () => console.error('DB open failed');

dbRequest.onsuccess = (event) => {
  const db = event.target.result;
  console.log('Database opened');
};

// Create object store (on version upgrade)
dbRequest.onupgradeneeded = (event) => {
  const db = event.target.result;

  // Create object store with primary key
  if (!db.objectStoreNames.contains('users')) {
    const userStore = db.createObjectStore('users', { keyPath: 'id' });

    // Create indexes
    userStore.createIndex('email', 'email', { unique: true });
    userStore.createIndex('name', 'name', { unique: false });
  }
};

// Add/update data
function addUser(db, user) {
  const transaction = db.transaction(['users'], 'readwrite');
  const store = transaction.objectStore('users');
  const request = store.add(user); // or put() to overwrite

  request.onsuccess = () => console.log('User added');
  request.onerror = () => console.error('Add failed');
}

// Retrieve by primary key
function getUser(db, id) {
  const transaction = db.transaction(['users'], 'readonly');
  const store = transaction.objectStore('users');
  const request = store.get(id);

  request.onsuccess = () => {
    console.log('User:', request.result);
  };
}

// Query by index
function getUserByEmail(db, email) {
  const transaction = db.transaction(['users'], 'readonly');
  const store = transaction.objectStore('users');
  const index = store.index('email');
  const request = index.get(email);

  request.onsuccess = () => {
    console.log('Found user:', request.result);
  };
}

// Get all records
function getAllUsers(db) {
  const transaction = db.transaction(['users'], 'readonly');
  const store = transaction.objectStore('users');
  const request = store.getAll();

  request.onsuccess = () => {
    console.log('All users:', request.result);
  };
}

// Range query
function getUsersInRange(db, minId, maxId) {
  const transaction = db.transaction(['users'], 'readonly');
  const store = transaction.objectStore('users');
  const range = IDBKeyRange.bound(minId, maxId);
  const request = store.getAll(range);

  request.onsuccess = () => {
    console.log('Users in range:', request.result);
  };
}

// Delete record
function deleteUser(db, id) {
  const transaction = db.transaction(['users'], 'readwrite');
  const store = transaction.objectStore('users');
  const request = store.delete(id);

  request.onsuccess = () => console.log('User deleted');
}

// Clear entire store
function clearUsers(db) {
  const transaction = db.transaction(['users'], 'readwrite');
  const store = transaction.objectStore('users');
  const request = store.clear();

  request.onsuccess = () => console.log('Store cleared');
}

// Promise wrapper (modern approach)
function openDB() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('myAppDB', 1);
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

async function addUserAsync(user) {
  const db = await openDB();
  return new Promise((resolve, reject) => {
    const transaction = db.transaction(['users'], 'readwrite');
    const request = transaction.objectStore('users').add(user);
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

Tips

  • Asynchronous: Non-blocking; use callbacks or Promises
  • Limit: Typically 50MB+ (varies by browser; can request more)
  • Transactions: Atomic operations; all-or-nothing
  • Indexes: Enable fast queries without scanning all records
  • Cursor iteration: Efficiently loop through large datasets
  • Best for: Offline-first apps, large datasets, complex queries, caching

Comparison Table

FeatureLocalStorageSessionStorageIndexedDB
Capacity5-10MB5-10MB50MB+
PersistenceUntil clearedTab closeUntil cleared
AsyncNoNoYes
QueryableNoNoYes
TransactionsNoNoYes
IndexesNoNoYes
Cross-tabYesNoYes
Best UsePreferencesTemp stateLarge data

Practical Example: Offline Todo App

```javascript class TodoApp { constructor() { this.db = null; this.init(); }

async init() { this.db = await this.openDB(); }

openDB() { return new Promise((resolve, reject) => { const request = indexedDB.open('TodoDB', 1);

request.onupgradeneeded = (e) => { const db = e.target.result; if (!db.objectStoreNames.contains('todos')) { const store = db.createObjectStore('todos', { keyPath: 'id' }); store.createIndex('completed', 'completed'); store.createIndex('date', 'date'); } };

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

async addTodo(title) { const todo = { id: Date.now(), title, completed: false, date: new Date().toISOString() };

return new Promise((resolve, reject) => { const tx = this.db.transaction(['todos'], 'readwrite'); const request = tx.objectStore('todos').add(todo); request.onsuccess = () => resolve(todo); request.onerror = () => reject(request.error); }); }

async getTodos() { return new Promise((resolve, reject) => { const tx = this.db.transaction(['todos'], 'readonly'); const request = tx.objectStore('todos').getAll(); request.onsuccess = () => resolve(request.result); request.onerror = () => reject(request.error); }); }

async toggleTodo(id) { return new Promise((resolve, reject) => { const tx = this.db.transaction(['todos'], 'readwrite'); const store = tx.objectStore('todos'); const getRequest = store.get(id);

getRequest.onsuccess = () => { const todo = getRequest.result; todo.completed = !todo.completed; const updateRequest = store.put(todo); updateRequest.onsuccess = () => resolve(todo); updateRequest.onerror = () => reject(updateRequest.error); }; }); } }

// Usage const app = new TodoApp(); await app.addTodo('Learn IndexedDB'); const todos = await app.getTodos();