Jagadhiswaran Devaraj

Mar 23, 2025 • 5 min read

IndexedDB Deep Dive: Unlocking Advanced Client-Side Storage

Everything you need to know about using IndexedDB for building scalable, persistent, and offline-first browser apps.

If you've ever tried building a web app that stores a lot of data on the client side, you've probably hit the limits of localStorage. It works fine for small key-value pairs, but what if you want to store thousands of records, or even blobs like images or files? That’s where IndexedDB comes in.

In this article, we’ll walk through what IndexedDB is, why you might use it, how it works under the hood, and some practical code examples to help you get started.

So, What Exactly Is IndexedDB?

IndexedDB is a low-level API provided by modern browsers that lets you store a significant amount of structured data, all within the user's browser. It’s asynchronous, non-blocking, and supports transactions, which means your operations won’t freeze the main thread of your application.

It's built on a NoSQL paradigm — rather than storing data in tables and rows like traditional relational databases, it stores data as key-value pairs in object stores. This makes it highly flexible for a wide variety of use cases.

Unlike localStorage, which can only store strings and has a limit of around 5MB, IndexedDB can store almost anything — JavaScript objects, arrays, binary data like images, and even large files. And it can handle hundreds of megabytes of data, depending on the browser and available disk space.

Why Use IndexedDB?

You should consider IndexedDB when:

  • You’re storing a large amount of data (for example, storing product catalogs, notes, or messages offline).

  • You need to store objects instead of strings.

  • You want to search or filter data by fields (e.g., get all messages where sender is "John").

  • You want better performance for complex data operations.

IndexedDB is especially useful in Progressive Web Apps (PWAs), offline-first apps, and browser-based databases where you want fast, persistent local access to structured data.

How IndexedDB Works: Under the Hood

IndexedDB operates around the idea of databases, object stores, and indexes:

  • Database: The main container. You can have multiple databases in a single app.

  • Object Store: Similar to a table in SQL. It holds a set of data records. Each record has a key and a value.

  • Key: A unique identifier for each record. It can be an auto-incremented number or a specific property like id.

  • Index: A way to search within object stores on fields other than the key.

  • Transaction: All read/write operations must occur within a transaction. Transactions are atomic and consistent, ensuring your data remains reliable.

Setting Up IndexedDB (With Code!)

Let’s break it down with some code. IndexedDB uses events and callbacks, so it can look verbose. However, you can later abstract it using Promises or libraries like idb.

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

request.onupgradeneeded = function (event) {
  const db = event.target.result;

  // Create an object store with a keyPath (like a primary key)
  const store = db.createObjectStore("Users", { keyPath: "id" });

  // Optional: create an index for querying by name
  store.createIndex("name", "name", { unique: false });
};

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

request.onerror = function (event) {
  console.error("Error opening database:", event.target.error);
};

The open() call takes the database name and version. If the version changes, onupgradeneeded fires so you can update the schema.

Adding Data

To store data, you first create a transaction and then interact with the object store.

const transaction = db.transaction(["Users"], "readwrite");
const store = transaction.objectStore("Users");

store.add({ id: 1, name: "Alice" });
store.add({ id: 2, name: "Bob" });

Transactions can be either readonly or readwrite. For adding or modifying data, readwrite is required.

Reading Data

Here’s how you read a single record by its key:

const transaction = db.transaction(["Users"], "readonly");
const store = transaction.objectStore("Users");
const request = store.get(1);

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

This will log the user with id = 1.

Querying with Index

If you created an index (like on name), you can query that too:

const transaction = db.transaction(["Users"], "readonly");
const store = transaction.objectStore("Users");
const index = store.index("name");
const request = index.get("Alice");

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

This allows searching without knowing the primary key — which is essential for advanced queries.

Handling Versions and Schema Changes

One important concept in IndexedDB is versioning. If you need to add a new object store or index, you must increment the version number. That triggers the onupgradeneeded event where you can define or change the schema.

const request = indexedDB.open("MyDatabase", 2); // version is now 2

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

This creates a new object store Messages when the database is upgraded from version 1 to 2.

Things to Keep in Mind

  • Asynchronous nature: You’ll deal with callbacks, or you can wrap operations in Promises to simplify.

  • Browser storage limits: While it supports larger sizes, browsers may have different storage policies.

  • Cross-origin isolation: Data stored in IndexedDB is sandboxed per origin — no sharing across domains.

  • Security: Data is stored locally and can’t be accessed by other apps or tabs (unless you build such a mechanism).

Bonus: Wrapping IndexedDB with Promises

You can make working with IndexedDB easier using Promises. Here’s a simple utility function:

function getUserById(db, id) {
  return new Promise((resolve, reject) => {
    const transaction = db.transaction(["Users"], "readonly");
    const store = transaction.objectStore("Users");
    const request = store.get(id);

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

This makes your code easier to read and chain.

Final Thoughts

IndexedDB gives you powerful tools to build offline-ready, high-performance web apps that can store and query large datasets. While the API is a bit clunky at first, once you understand the core concepts and wrap it properly, it becomes a go-to tool for client-side persistence.

If you’re building a PWA, want to cache lots of data, or just need a better alternative to localStorage, IndexedDB is definitely worth exploring.

- Jagadhiswaran Devaraj


📢 Stay Connected & Dive Deep into Tech!

🚀 Follow me for hardcore technical insights on JavaScript, Full-Stack Development, AI, and Scaling Systems:

🐦 X (Twitter): jags

✍️ Read more on Medium: https://medium.com/@jwaran78

💼 Connect with me on LinkedIn: https://www.linkedin.com/in/jagadhiswaran-devaraj/

Let’s geek out over code, architecture, and all things in tech! 💡🔥

Join Jagadhiswaran on Peerlist!

Join amazing folks like Jagadhiswaran and thousands of other people in tech.

Create Profile

Join with Jagadhiswaran’s personal invite link.

5

11

3