Skip to content

IndexedDB for Offline Storage

IndexedDB is a low-level, browser-native API for client-side storage, ideal for PWAs requiring robust offline capabilities. Unlike localStorage, it supports structured data, complex queries, and large datasets, making it suitable for apps like task managers, note-taking tools, or offline-first dashboards. This section covers schema design, CRUD operations, and synchronization strategies.


Schema Design Best Practices

1. Object Stores and Indexes

IndexedDB uses object stores (similar to tables) and indexes (for querying). Design your schema with these principles:

  • Single object store per entity: Use one store per data type (e.g., tasks for task data).
  • Indexes for filtering: Add indexes on frequently queried fields (e.g., taskStatus or dueDate).
  • Versioning: Use onupgradeneeded to handle schema migrations.

Example schema:

const request = indexedDB.open("MyPWA_DB", 1);
request.onupgradeneeded = function(event) {
  const db = event.target.result;
  if (!db.objectStoreNames.contains("tasks")) {
    db.createObjectStore("tasks", { keyPath: "id" });
    db.createObjectStore("users", { keyPath: "userId" });
  }
};

2. Data Modeling

  • Normalize data: Avoid redundant fields.
  • Use timestamps: Track lastModified for versioning.
  • Limit size: Use blob storage for large files (e.g., images) or split data into chunks.

CRUD Implementation

1. Adding Data

Use add() or put() to insert/update records.

function addTask(task) {
  return new Promise((resolve, reject) => {
    const db = indexedDB.open("MyPWA_DB");
    db.onsuccess = function(event) {
      const db = event.target.result;
      const transaction = db.transaction(["tasks"], "readwrite");
      const store = transaction.objectStore("tasks");
      const request = store.add(task);
      request.onsuccess = () => resolve(task.id);
      request.onerror = (e) => reject(e.target.error.name);
    };
    db.onerror = (e) => reject(e.target.error.name);
  });
}

2. Retrieving Data

Use indexes for efficient queries.

function getTasksByStatus(status) {
  return new Promise((resolve, reject) => {
    const db = indexedDB.open("MyPWA_DB");
    db.onsuccess = function(event) {
      const db = event.target.result;
      const transaction = db.transaction(["tasks"], "readonly");
      const store = transaction.objectStore("tasks");
      const index = store.index("status");
      const request = index.openCursor(IDBKeyRange.equal(status));
      const results = [];
      request.onsuccess = function(event) {
        const cursor = event.target.result;
        if (cursor) {
          results.push(cursor.value);
          cursor.continue();
        } else {
          resolve(results);
        }
      };
      request.onerror = (e) => reject(e.target.error.name);
    };
    db.onerror = (e) => reject(e.target.error.name);
  });
}

3. Deleting/Updating

Use delete() or put() with the primary key.


Synchronization Patterns

1. Offline-First Workflow

  • Cache data locally: Use IndexedDB to store user input.
  • Sync on connectivity: Use the Background Sync API to queue sync tasks.

Example: Sync when online

if (navigator.onLine) {
  syncTasks();
} else {
  console.log("No connection; data will sync later.");
}

2. Conflict Resolution

  • Timestamp-based merging: Use lastModified to prioritize newer data.
  • Server-side reconciliation: Push local changes to the backend and resolve conflicts.

Advanced Techniques

1. Versioning and Migration

Use onupgradeneeded to handle schema changes. For example, adding a new index:

db.onupgradeneeded = function(event) {
  const db = event.target.result;
  const store = db.createObjectStore("tasks", { keyPath: "id" });
  store.createIndex("status", "status", { unique: false });
};

2. Libraries for Simplification

Use libraries like Dexie.js to abstract IndexedDB complexity:

const db = new Dexie("MyPWA_DB");
db.version(1).stores({
  tasks: "++id, status, dueDate"
});

async function addTask(task) {
  await db.tasks.add(task);
}

Key takeaways

  • IndexedDB is essential for structured offline storage in PWAs.
  • Schema design should prioritize object stores, indexes, and versioning.
  • Synchronization requires offline-first workflows and conflict resolution strategies.
  • Libraries like Dexie.js can simplify complex operations.
  • Offline-first design ensures reliability even without network connectivity.