JavaScript IndexedDB Explained

JavaScript IndexedDB Explained

IndexedDB is a powerful built-in browser database that lets web applications store large amounts of structured data. Unlike simpler web storage solutions like localStorage or sessionStorage, IndexedDB supports complex objects and transactions, making it ideal for applications that require offline capabilities, such as games, note-taking apps, or any system needing persistent data beyond just simple strings. It operates asynchronously, so it does not block the main browser thread, ensuring smooth user experiences even when dealing with substantial amounts of data.

This database is persistent across browser sessions, meaning the data stays intact even if the browser or computer restarts. For example, Edward, who runs a chess app in Lusaka, can save each game’s moves locally using IndexedDB so that players can come back later and continue right where they left off. In this article, we will explore how to create, read, update, and delete data inside IndexedDB, all through engaging, real-world examples.

Opening (or Creating) an IndexedDB Database

To begin using IndexedDB, you must open a database. This is done using the indexedDB.open() method, which takes the database name and an optional version number. If the database does not exist, the browser will create it. Opening a database involves several important events: onsuccess, onerror, and onupgradeneeded.

Let’s say Edward wants to open a database called "ChessGames" to store his game data. Here’s how he can do it:

<!DOCTYPE html>
<html>
<head>

  <title>JavaScript | Indexed DB</title>

</head>
<body>

  <script>

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

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

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

    request.onupgradeneeded = function(event) {

      const db = event.target.result;
      console.log("Upgrading database...");

      // Create an object store here (we will do that in next section)
    };

  </script>

</body>
</html>

This code attempts to open the "ChessGames" database. If the database is new or if the version number is higher than the current one, the onupgradeneeded event fires, allowing you to set up object stores or modify the database schema. Success or error events inform you of the outcome.

Creating an Object Store

Inside the onupgradeneeded event, you create object stores, which are like tables in SQL databases. Each store can hold records with keys, and you define a keyPath that uniquely identifies each record.

Edward wants to create an object store called "games" to save chess game records, using "gameId" as the keyPath:

<!DOCTYPE html>
<html>
<head>

  <title>JavaScript | Indexed DB</title>

</head>
<body>

  <script>

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

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

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

    request.onupgradeneeded = function(event) {

      const db = event.target.result;

      if (!db.objectStoreNames.contains("games")) {
        const objectStore = db.createObjectStore("games", { keyPath: "gameId" });
        console.log("Object store 'games' created.");
      }

    };

  </script>

</body>
</html>

This code ensures the "games" store is created only if it does not already exist. Each record stored here must have a unique "gameId" property, which IndexedDB uses as the primary key.

Adding Data to the Object Store

To add data to IndexedDB, you open a transaction on the database, specify the store, and use the add() or put() methods. The difference is that add() will fail if the key exists, while put() adds or updates.

Now, Hermione wants to save a chess game record with moves to the "games" store:

<!DOCTYPE html>
<html>
<head>

  <title>JavaScript | Indexed DB</title>

</head>
<body>

  <script>

    function addGame(db, game) {

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

      const request = store.add(game);

      request.onsuccess = function () {
        console.log("Game added:", game);
      };

      request.onerror = function (event) {
        console.log("Error adding game:", event.target.error);
      };

    }

    const game = {
      gameId: "game123",
      playerWhite: "Hermione",
      playerBlack: "Ron",
      moves: ["e4", "e5", "Nf3", "Nc6"],
      date: new Date()
    };


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

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

    request.onsuccess = function(event) {

      const db = event.target.result;
      addGame(db, game);

    };

  </script>

</body>
</html>

This example opens a read-write transaction, accesses the "games" store, and adds a game object. It includes information like players, moves, and the game date. The success and error handlers confirm if the record was stored.

Reading Data from IndexedDB

Reading data involves opening a transaction and using the get() method with a key to fetch a specific record.

Ron wants to retrieve the chess game "game123" stored by Hermione:

<!DOCTYPE html>
<html>
<head>

  <title>JavaScript | Indexed DB</title>

</head>
<body>

  <script>

    function getGame(db, gameId) {

      const transaction = db.transaction(["games"], "readonly");
      const store = transaction.objectStore("games");

      const request = store.get(gameId);

      request.onsuccess = function (event) {

        const game = event.target.result;

        if (game) {
          console.log("Game retrieved:", game);
        } else {
          console.log("Game not found.");
        }

      };

      request.onerror = function (event) {
        console.log("Error fetching game:", event.target.error);
      };

    }

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

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

    // After opening the db
    request.onsuccess = function (event) {

      const db = event.target.result;
      getGame(db, "game123");

    };

  </script>

</body>
</html>

This code retrieves the game with the key "game123" and logs it if found. The transaction is read-only since no data is modified.

Updating Data in IndexedDB

Updating data is done with the put() method, which adds or replaces an existing record.

Chris wants to update the moves of a game when a player makes a new move:

<!DOCTYPE html>
<html>
<head>

  <title>JavaScript | Indexed DB</title>

</head>
<body>

  <script>

    function updateGameMoves(db, gameId, newMoves) {

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

      const getRequest = store.get(gameId);

      getRequest.onsuccess = function (event) {

        const game = event.target.result;

        if (game) {

          game.moves = newMoves;
          const updateRequest = store.put(game);

          updateRequest.onsuccess = function () {
            console.log("Game moves updated:", game);
          };

          updateRequest.onerror = function (event) {
            console.log("Error updating game:", event.target.error);
          };

        } else {
          console.log("Game to update not found.");
        }

      };

    }

    const request = indexedDB.open("ChessGames", 3); // Database updated

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

    // After opening db
    request.onsuccess = function (event) {

      const db = event.target.result;
      updateGameMoves(db, "game123", ["e4", "e5", "Nf3", "Nc6", "Bb5"]);

    };

  </script>

</body>
</html>

This code first fetches the existing game, modifies its moves array, and then uses put() to save the changes back to the database.

Deleting Data from IndexedDB

Deleting data is done with the delete() method on the object store.

Molly wants to remove a finished chess game "game123" from the "games" store:

<!DOCTYPE html>
<html>
<head>

  <title>JavaScript | Indexed DB</title>

</head>
<body>

  <script>

    function deleteGame(db, gameId) {

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

      const request = store.delete(gameId);

      request.onsuccess = function() {
        console.log(`Game ${gameId} deleted.`);
      };

      request.onerror = function(event) {
        console.log("Error deleting game:", event.target.error);
      };

    }

    const request = indexedDB.open("ChessGames", 3); // Database updated

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

    // After opening db
    request.onsuccess = function(event) {

      const db = event.target.result;
      deleteGame(db, "game123");

    };

  </script>

</body>
</html>

The delete() method removes the record by its key and reports success or failure.

Using Cursors to Iterate Over Data

When you want to access multiple records, cursors let you iterate through an object store or an index.

Edward wants to list all chess games saved in the database:

<!DOCTYPE html>
<html>
<head>

  <title>JavaScript | Indexed DB</title>

</head>
<body>

  <script>

    function listAllGames(db) {

      const transaction = db.transaction(["games"], "readonly");
      const store = transaction.objectStore("games");

      const request = store.openCursor();

      request.onsuccess = function(event) {

        const cursor = event.target.result;

        if (cursor) {

          console.log("Game:", cursor.value);
          cursor.continue();

        } else {
          console.log("No more games.");
        }

      };

      request.onerror = function(event) {
        console.log("Error iterating games:", event.target.error);
      };

    }

    const request = indexedDB.open("ChessGames", 3); // Database updated

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

    // After opening db
    request.onsuccess = function(event) {

      const db = event.target.result;
      listAllGames(db);

    };

  </script>

</body>
</html>

This code opens a cursor that traverses each game record and logs it until all have been read.

Listing All IndexedDB Databases

Sometimes, you may want to inspect all existing databases in the browser—useful for debugging, development, or building admin-like features.

Let’s say Glenn wants to view all databases available in his browser while testing an app in Lusaka. He can use the indexedDB.databases() method, which returns a list of all databases the browser is aware of:

<!DOCTYPE html>
<html>
<head>
  <title>List IndexedDB Databases</title>
</head>
<body>

  <h3>List All Databases</h3>
  <button onclick="listDatabases()">List Databases</button>
  <ul id="dbList"></ul>

  <script>

    function listDatabases() {

      const dbList = document.getElementById("dbList");
      dbList.innerHTML = "";

      if (indexedDB.databases) {

        indexedDB.databases().then((dbs) => {

          if (dbs.length === 0) {

            dbList.innerHTML = "<li>No databases found.</li>";
          } else {

            dbs.forEach((db) => {

              const item = document.createElement("li");
              item.textContent = `Name: ${db.name}, Version: ${db.version}`;
              dbList.appendChild(item);

            });

          }

        }).catch((error) => {

          dbList.innerHTML = "<li>Error listing databases.</li>";
          console.error("Error:", error);

        });

      } else {
        dbList.innerHTML = "<li>Your browser doesn't support indexedDB.databases()</li>";
      }

    }

  </script>
</body>
</html>

This feature works in most modern browsers like Chrome and Firefox. Just click the button to view all IndexedDB databases and their versions.

Deleting an IndexedDB Database

Sometimes you need to reset your database—maybe when changing data structures or clearing user history. You can delete a whole database using indexedDB.deleteDatabase().

Samantha wants to delete her "ChessGames" database to start fresh. Here’s how she does it:

<!DOCTYPE html>
<html>
<head>
  <title>Delete IndexedDB Database</title>
</head>
<body>

  <h3>Delete 'ChessGames' Database</h3>
  <button onclick="deleteDatabase()">Delete Database</button>
  <p id="statusMessage"></p>

  <script>

    function deleteDatabase() {

      const status = document.getElementById("statusMessage");
      const deleteRequest = indexedDB.deleteDatabase("ChessGames");

      deleteRequest.onsuccess = function () {
        status.textContent = "'ChessGames' database deleted successfully.";
        console.log("Database deleted.");
      };

      deleteRequest.onerror = function (event) {
        status.textContent = "Error deleting database.";
        console.error("Error:", event.target.error);
      };

      deleteRequest.onblocked = function () {
        status.textContent = "Deletion blocked. Please close other tabs or windows using this database.";
        console.warn("Delete blocked.");
      };

    }

  </script>

</body>
</html>

This code lets you delete an entire database by clicking a button. It’s useful during development or when giving users the option to clear all data.

Writing a Simple Stat Tracker Using IndexedDB

Stewie wants to track how many chess games he has played across multiple browsing sessions in Nairobi. IndexedDB’s persistence allows this.

Here’s a simple stat tracker that reads, increments, and stores a total games played count:

<!DOCTYPE html>
<html>
<head>

  <title>JavaScript | Indexed DB</title>

</head>
<body>

  <script>

    function trackGamesPlayed(db) {

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

      const getRequest = store.get("totalGames");

      getRequest.onsuccess = function (event) {

        let totalGames = event.target.result ? event.target.result.count : 0;
        totalGames++;

        const putRequest = store.put({ id: "totalGames", count: totalGames });

        putRequest.onsuccess = function () {
          console.log(`Total games played: ${totalGames}`);
        };

        putRequest.onerror = function (event) {
          console.log("Error updating stats:", event.target.error);
        };

      };

      getRequest.onerror = function (event) {
        console.log("Error reading stats:", event.target.error);
      };

    }

    // Open or create DB with stats store
    const openRequest = indexedDB.open("ChessStats", 1);

    openRequest.onupgradeneeded = function (event) {

      const db = event.target.result;

      if (!db.objectStoreNames.contains("stats")) {
        db.createObjectStore("stats", { keyPath: "id" });
      }

    };

    openRequest.onsuccess = function (event) {

      const db = event.target.result;
      trackGamesPlayed(db);

    };

  </script>

</body>
</html>

This code opens a "ChessStats" database with a "stats" store, retrieves the current count of games played (or 0 if none), increments it, and saves it back. The count persists through browser restarts.

Conclusion

IndexedDB provides a robust, asynchronous, and persistent storage solution in browsers for structured data like objects and arrays. Starting by opening a database and creating object stores, you can add, read, update, and delete records within transactions. Cursors enable iteration over large datasets, and you can even build features like persistent stat trackers that survive browser sessions. This makes IndexedDB perfect for complex web applications such as Edward’s chess app or Stewie’s game tracker, where rich data management is essential.

References

If you want to learn more about JavaScript IndexedDB and related topics, these resources are excellent starting points:

Scroll to Top