IndexedDB - browsers own storage system.

Viraj NikamViraj Nikam
4 min read

Introduction.

In a recent article, I introduced Dexie.js, a wrapper library for IndexedDB that simplifies working with it. I should have published this article about using IndexedDB with native APIs before the Dexie.js article. Why? Because only when you experience the complexity and pain of writing native IndexedDB queries will you truly appreciate the abstraction Dexie.js provides.

So let’s not dive into Dexie.js again I’ve already discussed it in detail in my earlier post. If you're curious about how Dexie helps simplify things, I recommend reading that article after finishing this one.

Why IndexedDB?

You might wonder: if we have localStorage which is easy to use, why do we need IndexedDB at all?

That’s a fair question but only partially correct. Let's talk about localStorage first:

  • localStorage has a limited capacity, usually around 5–10MB.

  • Its operations are synchronous, meaning large data inserts or updates can block the UI and make the app unresponsive.

  • It’s suitable for small, frequent, and simple data operations.

In contrast, IndexedDB offers:

  • Asynchronous operations which means it doesn’t block the main thread.

  • Support for structured data stored in object stores (which you can think of as tables).

  • Indexed querying, allowing you to query data more efficiently.

  • Support for versioning and schema upgrades.

NOTE : WE CAN ONLY INCREMENT THE DATABASE VERSION, IF WE TRY TO DECREMENT THE VERSION THE onerror EVENT WILL TRIGGER.

Let’s Code: Setting Up IndexedDB with Native API

IndexedDB uses an event-driven architecture. So, the sequence in which you write operations doesn’t matter as much as how you handle events.

// Open a database connection
const database = indexedDB.open("MyDatabase", 1); 
// Pass: database name, and version number

step 1 : Create objectStore (Tables) and Indexes.

database.onupgradeneeded = (event) => {
    const db = event.target.result;

    if (!db.objectStoreNames.contains("students")) {
        const store = db.createObjectStore("students", { keyPath: "id" });

        // Indexes: (indexName, keyPath, options)
        store.createIndex("name", "name", { unique: false });
        store.createIndex("email", "email", { unique: true });
    }
};

step 2 : Insert Data

database.onsuccess = (event) => {
    const db = event.target.result;

    const transaction = db.transaction("students", "readwrite");
    const store = transaction.objectStore("students");

    const request = store.add({
        id: 1,
        name: "Viraj Nikam",
        email: "viraj@gmail.com"
    });

    request.onsuccess = (event) => {
        console.log("Data inserted successfully!", event.target.result);
    };

    request.onerror = (event) => {
        console.error("Failed to insert data:", event.target.error);
    };
};

IndexedDB Native API – CRUD Operations

1. Add Data (Create)

const request = store.add({
    id: 1,
    name: "Viraj Nikam",
    email: "viraj@gmail.com"
});

2. Get Data (Read)

a. Get by primary key (id)

const getRequest = store.get(1);

getRequest.onsuccess = (event) => {
    console.log("Record fetched:", event.target.result);
};

getRequest.onerror = (event) => {
    console.error("Error fetching record:", event.target.error);
};

b. Get by Index

const index = store.index("email");
const emailRequest = index.get("viraj@gmail.com");

emailRequest.onsuccess = (event) => {
    console.log("Record fetched by email:", event.target.result);
};

3. Update Data

To update, you first fetch the record, modify it, and then use put() (not add(), since add() will fail if the key already exists):

const getRequest = store.get(1);

getRequest.onsuccess = (event) => {
    const data = event.target.result;
    data.name = "Viraj S. Nikam"; // Updating the name

    const updateRequest = store.put(data);

    updateRequest.onsuccess = () => {
        console.log("Record updated successfully!");
    };
};

4. Delete Data

const deleteRequest = store.delete(1);

deleteRequest.onsuccess = () => {
    console.log("Record deleted successfully!");
};

deleteRequest.onerror = (event) => {
    console.error("Failed to delete record:", event.target.error);
};

Final Execution :

const database = indexedDB.open("MyDatabase", 1) // Here you need to pass name of the database and it's version

// add tables to your db over here at onupgradeneeded
database.onupgradeneeded((evt)=>{
    const dbInstance = evt.target.result;

    if(!dbInstance.objectStoreNames.contains("students")){
            const request = dbInstance.createObjectStore("students", {keyPath : "id"});
            //createIndex used to define the columns, Its necessary to define Indexes because it helps you at time of queries
            request.createIndex("name", "name", {unique : false}) // takes column name, accessar key for that perticular column, and object of the constraints
            request.createIndex("email", "email", {unique : true})
    }
})

//perform your queries over here
database.onsuccess = (event) => {
    const db = event.target.result;
    const transaction = db.transaction("students", "readwrite");
    const store = transaction.objectStore("students");

    // Add
    const addRequest = store.add({
        id: 2,
        name: "Jane Doe",
        email: "jane@example.com"
    });

    // Read by ID
    const getRequest = store.get(2);
    getRequest.onsuccess = () => console.log("Fetched:", getRequest.result);

    // Update
    getRequest.onsuccess = () => {
        const data = getRequest.result;
        data.name = "Jane D.";
        const updateRequest = store.put(data);
        updateRequest.onsuccess = () => console.log("Updated!");
    };

    // Delete
    const deleteRequest = store.delete(1);
    deleteRequest.onsuccess = () => console.log("Deleted record with ID 1");
};

Wrapping Up.

Working with native IndexedDB can feel a bit messy and confused due to its event-based flow. That’s exactly why libraries like Dexie.js exist to make your life easier.

But understanding the native API is crucial for debugging, customization, and appreciating how much a wrapper is actually helping you.

Thank You !☺️

0
Subscribe to my newsletter

Read articles from Viraj Nikam directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Viraj Nikam
Viraj Nikam