Technical Considerations and Implementation of Client-Side Persistence with IndexedDB

Architectural analysis of IndexedDB for large-scale data persistence in browser environments. Technical explanation of the implementation structure in src/db/indexedDb.ts, comparison with LocalStorage, and key points of offline-first design.

Building and Implementation Details of Offline-First Architecture with IndexedDB

In web application design, architectures that synchronize all user data to a central server are not always optimal from the perspectives of network latency, privacy protection, and infrastructure costs. Especially when handling personal data such as diaries, ensuring offline operation and maintaining data sovereignty is essential. The implementation in src/db/indexedDb.ts is not merely a definition of sample data, but serves as a foundation for abstracting the browser’s physical storage and functioning as a persistent data store.

1. Technical Background for Adopting IndexedDB

While modern web storage options include LocalStorage and SessionStorage, the choice of IndexedDB for this implementation is based on the following constraints:

  • Storage Capacity Scalability: LocalStorage has a limit of approximately 5MB, whereas IndexedDB allows for large-scale data storage (on the order of hundreds of MBs to GBs) depending on the device’s disk capacity.
  • Structured Data Management: By utilizing object stores and indexes, complex search queries and sorting can be executed at high speeds.
  • Main Thread Protection via Asynchronous I/O: Since all operations are performed asynchronously, UI rendering is not obstructed even during large-scale data processing.

2. src/db/indexedDb.ts Implementation Details

This is a wrapper implementation of IndexedDB using TypeScript. It defines database initialization, transaction management, and abstraction of CRUD operations.

export interface DiaryEntry {
  id?: number;
  title: string;
  content: string;
  createdAt: number;
  updatedAt: number;
}

const DB_NAME = 'DiaryAppDB';
const DB_VERSION = 1;
const STORE_NAME = 'entries';

export class DiaryDB {
  private db: IDBDatabase | null = null;

  public async open(): Promise<IDBDatabase> {
    return new Promise((resolve, reject) => {
      const request = indexedDB.open(DB_NAME, DB_VERSION);

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

      request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
        const db = (event.target as IDBOpenDBRequest).result;
        if (!db.objectStoreNames.contains(STORE_NAME)) {
          const store = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true });
          store.createIndex('createdAt', 'createdAt', { unique: false });
        }
      };
    });
  }

  public async addEntry(entry: Omit<DiaryEntry, 'id'>): Promise<number> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, 'readwrite');
      const store = transaction.objectStore(STORE_NAME);
      const request = store.add(entry);

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

  public async getAllEntries(): Promise<DiaryEntry[]> {
    const db = await this.getDB();
    return new Promise((resolve, reject) => {
      const transaction = db.transaction(STORE_NAME, 'readonly');
      const store = transaction.objectStore(STORE_NAME);
      const index = store.index('createdAt');
      const request = index.getAll();

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

  private async getDB(): Promise<IDBDatabase> {
    if (this.db) return this.db;
    return this.open();
  }
}

3. Integration of PWA and Offline-First

IndexedDB serves as the core of the “offline-first” strategy in Progressive Web Apps (PWA). Combined with Service Workers, users can view and edit data even in environments where the network is disconnected (such as subways or airplane mode). Data is written immediately to local hardware, enabling a design where synchronization with an external server occurs as needed upon returning online.

4. Troubleshooting: Operational Friction Points

The following are typical challenges developers face when implementing IndexedDB and their countermeasures.

  • ⚠️ Schema Change Conflicts: Store creation and deletion are only possible within the onupgradeneeded event. Incorrect version management can lead to the loss of existing data or connection errors.
  • ⚠️ Quota Limits (QuotaExceededError): Writes will fail if the browser’s free space is insufficient. It is recommended to implement logic that checks available capacity in advance using StorageManager.estimate().
  • ⚠️ Automatic Transaction Commit: Inserting asynchronous processing (such as setTimeout or external API calls) within a transaction causes the transaction to close automatically, leading to errors.

5. Operation Verification Protocol

The following are the database initialization and data integrity verification logs in the development environment.

$ npm run build:ts
$ node --check src/db/indexedDb.ts

# Browser Console Verification Log
[DB] Opening IndexedDB: DiaryAppDB (Version: 1)
[DB] Upgrade needed: Creating ObjectStore 'entries'
[DB] Success: Database connection established.
[DB] Transaction started: readwrite on 'entries'
[DB] Entry added: ID=1, Title="Sample Entry"
[DB] Query result: 1 records found in 1.2ms

# Storage Quota Check
$ curl -I http://localhost:3000
HTTP/1.1 200 OK
Service-Worker-Allowed: /
Cache-Control: no-cache

Operational Notes

🛠️ src/db/indexedDb.ts is not just a data access layer, but a critical component that defines the application’s “privacy sandbox.” By adopting a serverless architecture, users can keep their data under complete control, and developers can significantly reduce server maintenance costs and security risks. As a future enhancement, implementing local encryption using the WebCrypto API can enable even more robust data protection.

Built with Hugo
Theme Stack designed by Jimmy
Privacy Policy Disclaimer Contact