DEV Community

Jorj Hambaryan
Jorj Hambaryan

Posted on

IndexedDB

What Question Does IndexedDB Solve?


IndexedDB is a solution to store large amounts of structured data, even offline. It solves the problem of efficient local storage for web applications, enabling them to access, update, and query large datasets directly on the client side.


Key Problems IndexedDB Solves:

  • Persistent local storage of structured data.
  • Efficient storage and retrieval of large datasets.
  • Functionality across offline and online modes.
  • Asynchronous access to data, preventing blocking of the main thread.

Code Example:

// Open an IndexedDB database
let request = indexedDB.open("MyDatabase", 1);

request.onupgradeneeded = function(event) {
  let db = event.target.result;
  // Create an object store
  let objectStore = db.createObjectStore("users", { keyPath: "id" });
  objectStore.createIndex("name", "name", { unique: false });
};

request.onsuccess = function(event) {
  let db = event.target.result;
  console.log("Database opened successfully");
};
Enter fullscreen mode Exit fullscreen mode

How to Use IndexedDB

Open the Database: Open an IndexedDB database with the indexedDB.open() function.
Create Object Stores: Object stores are similar to tables in relational databases.
Perform Transactions: Use transactions to add, retrieve, update, or delete data.
Index Creation: Indices are used for searching data within object stores.
Error Handling: Handle asynchronous operations with event listeners.

// Adding data to IndexedDB
let addData = function(db) {
  let transaction = db.transaction(["users"], "readwrite");
  let objectStore = transaction.objectStore("users");

  let user = { id: 1, name: "Alice", email: "alice@example.com" };
  let request = objectStore.add(user);

  request.onsuccess = function() {
    console.log("User added to IndexedDB!");
  };
};

// Retrieve data
let getData = function(db) {
  let transaction = db.transaction(["users"], "readonly");
  let objectStore = transaction.objectStore("users");

  let request = objectStore.get(1);
  request.onsuccess = function(event) {
    console.log("User:", event.target.result);
  };
};
Enter fullscreen mode Exit fullscreen mode

Advantages of IndexedDB
IndexedDB offers several key advantages that make it ideal for modern web applications

  • Large Storage Capacity: IndexedDB can store more data compared to localStorage or sessionStorage

Image description

  • Asynchronous Access: Prevents blocking the UI by using non-blocking I/O operations.

  • Complex Data Handling: Supports structured data, including arrays, objects, and blobs.

  • Offline Support: Works both online and offline, allowing for Progressive Web App (PWA) use cases.

  • Indexed Searches: Data can be efficiently searched via indexes.
    Image description

Image description

// Create an index to improve search performance
objectStore.createIndex("email", "email", { unique: true });

let emailIndex = objectStore.index("email");
let request = emailIndex.get("alice@example.com");

request.onsuccess = function(event) {
  console.log("User found by email:", event.target.result);
};

Enter fullscreen mode Exit fullscreen mode

Image description

*Understanding IndexedDB Indexes
*

What are IndexedDB Indexes?
IndexedDB indexes are additional data structures within an object store that allow efficient retrieval of data based on specific properties. An index consists of a key path (one or more properties) and options to define uniqueness and sorting order. By creating indexes, you can optimize data querying and filtering operations, leading to improved performance.

Importance of Indexes in IndexedDB
Indexes play a crucial role in enhancing data retrieval performance in IndexedDB. Without indexes, querying data based on specific properties would require iterating over all records, resulting in slower and less efficient data retrieval. Indexes enable direct access to data based on indexed properties, reducing the need for full table scans and significantly improving query execution speed.

let transaction = db.transaction("MyObjectStore", "readonly");
let objectStore = transaction.objectStore("MyObjectStore");
let index = objectStore.index("nameIndex");

let cursorRequest = index.openCursor();

cursorRequest.onsuccess = function(event) {
  let cursor = event.target.result;
  if (cursor) {
    // Access the current record
    console.log(cursor.value);

    // Move to the next record
    cursor.continue();
  }
};
Enter fullscreen mode Exit fullscreen mode

Filtering Data with Range Queries IndexedDB
Indexes also enable range queries, allowing you to filter data based on a range of values. Here’s an example of retrieving records within a specific range of ages:

let transaction = db.transaction("MyObjectStore", "readonly");
let objectStore = transaction.objectStore("MyObjectStore");
let index = objectStore.index("ageIndex");

let range = IDBKeyRange.bound(18, 30); // Records with age between 18 and 30

let cursorRequest = index.openCursor(range);

cursorRequest.onsuccess = function(event) {
  let cursor = event.target.result;
  if (cursor) {
    // Process the record within the desired age range
    console.log(cursor.value);

    // Move to the next record
    cursor.continue();
  }
};

Enter fullscreen mode Exit fullscreen mode

Sorting Data with Indexes

Indexes can also be utilized for sorting data in IndexedDB. By opening a cursor on an index and specifying the desired sorting order, you can retrieve records in the desired order.

let transaction = db.transaction("MyObjectStore", "readonly");
let objectStore = transaction.objectStore("MyObjectStore");
let index = objectStore.index("nameIndex");

let cursorRequest = index.openCursor(null, "next"); // Sorted in ascending order

cursorRequest.onsuccess = function(event) {
  let cursor = event.target.result;
  if (cursor) {
    // Process the record
    console.log(cursor.value);

    // Move to the next record
    cursor.continue();
  }
};
Enter fullscreen mode Exit fullscreen mode

Index Maintenance and Schema Upgrades

Adding New Indexes to Existing Object Stores
To add new indexes to an existing object store, you need to handle the database upgrade process. Here’s an example of adding a new index during a database upgrade.

let request = indexedDB.open("MyDatabase", 2);

request.onupgradeneeded = function(event) {
  let db = event.target.result;
  let objectStore = db.createObjectStore("MyObjectStore", { keyPath: "id" });

  // Add a new index during upgrade
  objectStore.createIndex("newIndex", "newProperty", { unique: false });
};
Enter fullscreen mode Exit fullscreen mode

In this example, we upgrade the database version from 1 to 2 and add a new index named “newIndex” on the “newProperty” property.


Performance Considerations and Best Practices
Choosing the Right IndexedDB Indexes
When creating indexes, it’s important to carefully choose the properties to index based on your application’s data access patterns. Indexes should be created on properties commonly used for filtering, sorting, or searching to maximize their benefits.

Limiting Index Size and Performance Impact
Large indexes can consume significant storage space and impact performance. It’s advisable to limit the number of indexes and choose key paths that are necessary for efficient data retrieval. Avoid excessive indexing to maintain optimal performance.

Monitoring Index Performance
Regularly monitor the performance of your IndexedDB indexes using browser developer tools and profiling techniques. Identify any slow queries or bottlenecks and optimize your indexes accordingly. It may involve modifying the index structure or adding additional indexes to improve query execution speed.


Cursor in IndexedDB

let request = indexedDB.open("MyDatabase", 1);

request.onsuccess = function(event) {
  let db = event.target.result;
  let transaction = db.transaction(["users"], "readonly");
  let objectStore = transaction.objectStore("users");

  let cursorRequest = objectStore.openCursor();
  let batchSize = 10;
  let count = 0;

  cursorRequest.onsuccess = function(event) {
    let cursor = event.target.result;
    if (cursor) {
      console.log("Key:", cursor.key, "Value:", cursor.value);
      count++;

      // If batch size is reached, stop processing
      if (count < batchSize) {
        cursor.continue();
      } else {
        console.log("Batch processing limit reached. Pausing...");
        // Optionally, you could set up a mechanism to continue later.
      }
    } else {
      console.log("No more entries!");
    }
  };
};

Enter fullscreen mode Exit fullscreen mode

IndexedDB Sharding

Sharding is a technique, normally used in server side databases, where the database is partitioned horizontally. Instead of storing all documents at one table/collection, the documents are split into so called shards and each shard is stored on one table/collection. This is done in server side architectures to spread the load between multiple physical servers which increases scalability.

When you use IndexedDB in a browser, there is of course no way to split the load between the client and other servers. But you can still benefit from sharding. Partitioning the documents horizontally into multiple IndexedDB stores, has shown to have a big performance improvement in write- and read operations while only increasing initial pageload slightly.

Image description


Conclusion
IndexedDB represents a paradigm shift in browser-based storage, empowering developers to build powerful web applications with offline capabilities and efficient data management. By embracing IndexedDB and adhering to best practices, developers can unlock new possibilities in web development, delivering richer and more responsive user experiences across a variety of platforms and devices. As the web continues to evolve, IndexedDB stands as a testament to the innovative solutions driving the next generation of web applications.


Top comments (0)