PWAs have several options for storing data on the client:
| API | Type | Best For |
|---|---|---|
| localStorage | Sync key-value | Preferences, small data |
| sessionStorage | Sync key-value | Session-only data |
| IndexedDB | Async NoSQL | Complex data, large datasets |
| Cache API | HTTP responses | Network response caching |
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);
};
});
}
// 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);
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)
});
}
}