← Back to Tutorials Chapter 10

Offline Data Management

Storage Options

PWAs have several options for storing data on the client:

APITypeBest For
localStorageSync key-valuePreferences, small data
sessionStorageSync key-valueSession-only data
IndexedDBAsync NoSQLComplex data, large datasets
Cache APIHTTP responsesNetwork response caching

IndexedDB

IndexedDB is a low-level, asynchronous NoSQL database in the browser. It can store large amounts of structured data, including files and blobs.

// Open a database
const request = indexedDB.open('MyPWA', 1);

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // Create object store
  const store = db.createObjectStore('tasks', {
    keyPath: 'id',
    autoIncrement: true
  });
  // Create indexes
  store.createIndex('status', 'completed', { unique: false });
  store.createIndex('dueDate', 'dueDate', { unique: false });
};

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

// CRUD operations
function addTask(task) {
  const db = indexedDB.open('MyPWA', 1);
  db.onsuccess = (event) => {
    const db = event.target.result;
    const tx = db.transaction('tasks', 'readwrite');
    const store = tx.objectStore('tasks');
    store.add(task);
    tx.oncomplete = () => console.log('Task added');
  };
}

function getTasks() {
  return new Promise((resolve, reject) => {
    const db = indexedDB.open('MyPWA', 1);
    db.onsuccess = (event) => {
      const db = event.target.result;
      const tx = db.transaction('tasks', 'readonly');
      const store = tx.objectStore('tasks');
      const all = store.getAll();
      all.onsuccess = () => resolve(all.result);
      all.onerror = () => reject(all.error);
    };
  });
}

Using IndexedDB with Dexie.js (Simplified)

// Install: npm install dexie

import Dexie from 'dexie';

const db = new Dexie('MyPWA');

// Define schema
db.version(1).stores({
  tasks: '++id, name, completed, dueDate',
  notes: '++id, title, &url'
});

// CRUD
await db.tasks.add({ name: 'Learn PWAs', completed: false });
const tasks = await db.tasks.where('completed').equals(false).toArray();
await db.tasks.update(1, { completed: true });
await db.tasks.delete(1);

Background Sync

The Background Sync API allows you to defer actions until the user has stable connectivity.

// Register a sync event
async function registerSync() {
  const registration = await navigator.serviceWorker.ready;
  await registration.sync.register('sync-tasks');
}

// In Service Worker
self.addEventListener('sync', (event) => {
  if (event.tag === 'sync-tasks') {
    event.waitUntil(syncPendingTasks());
  }
});

async function syncPendingTasks() {
  const tasks = await getOfflineTasks();
  for (const task of tasks) {
    await fetch('/api/tasks', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(task)
    });
  }
}
Exercise: Build an offline-capable to-do list app that stores tasks in IndexedDB. When the user creates a task while offline, save it locally and sync it to a server when connectivity returns using Background Sync.