CRUD Operations with IndexedDB - Part 1: Creating and Clearing a Database

gwagner's picture

In this article, we show how to create and clear an IndexedDB database keeping all code in a single HTML file. Creating an IndexedDB database means first creating an empty database, and then adding records to its empty tables (or "object stores"). In Part 2, we will explain how to retrieve, update and delete IndexedDB records, such that we get the full collection of CRUD operations: Create, Update, Retrieve and Delete.

This is not an introduction to IndexedDB, but rather a guide how to use it for CRUD operations in a front-end app. We assume that the reader has already done an introductory tutorial such as Working with IndexedDB. Recall that an IndexedDB database is a set of object tables (or "object stores") where each table row, or record, has a standard ID (or "primary key") property defined with keyPath when creating the database.

As recommended in Working with IndexedDB, we use the IndexedDB Promised library, which wraps the indexedDB API with ES6-Promise/await-based methods for obtaining more readable and maintainable code in our IndexedDB access methods. Download it into your project folder such that it can be included with <script src="idb.js"></script> or load it from the CDN https://unpkg.com/idb/build/iife/index-min.js.

We extend the "idb" namespace variable by adding three database management methods:

idb.createEmptyDB( tableNames)
Checks if there is already a database with the given name, and, if not, creates one with an empty table (or "object store") for each of the table names provided.
idb.addRecords( tableName, records)
Adds a list of records to the given table.
idb.clearDB()
Clears the contents of all tables.

We start with looking at the code of the createEmptyDB method:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
 <meta charset="UTF-8" />
 <title>CRUD Operations with IndexedDB - Part 1</title>
 <!-- Load the IndexedDB Promised library -->
 <script src="idb.js"></script>
 <script>
  const dbName = "test";
  /*******************************************************************
   * Create an empty IndexedDB
   *******************************************************************/
  idb.createEmptyDB = async function (dbName, tableNames) {
   idb.db = await idb.openDB( dbName, 1, {
    upgrade(db) {
     for (const tn of tableNames) {
      if (!db.objectStoreNames.contains( tn)) {
       db.createObjectStore( tn, {keyPath:"id"});
      }
     }
    }
   });
  };

We define a global variable dbName as the name of our IndexedDB database. The idb.createEmptyDB method takes a list of table names and tries to open the database (with version number 1). If this fails, that is, if the database does not yet exist, the function with the paramter upgradeDb will be called, taking care of creating an empty object store with a standard ID property "id" for each of the table names provided.

For adding a list of records to a table (= object store) in one (transactional) step, we invoke the IndexedDB add method on the corresponding object store for each of them in parallel. Since each add invocation returns a promise object, we create a list of promise objects by mapping the records array to an array of add invocation expressions and use the Promise.all method for resolving this list of promises only when all of them have resolved:

  idb.addRecords = async function (tableName, records) {
    // create a transaction involving only the table with the provided name
    const tx = idb.db.transaction( tableName, "readwrite");
    // create a list of add invocation expressions
    const addInvocationExpressions = records.map( r => tx.store.add( r));
    // invoke all of them in parallel and wait for their completion
    await Promise.all( addInvocationExpressions);
    // wait for the completion of the transaction tx
    await tx.done;
  };

For clearing the entire database contents, we need to clear all object tables/stores by invoking the IndexedDB clear method on them. Again, we create a list of promises by mapping the idbCx.objectStoreNames collection to an array of clear invocation return values and use the Promise.all method for resolving this list only when all of them have resolved:

  idb.clearDB = async function (dbName) {
    // only open DB and store a connection reference if not yet done
    idb.db ??= await idb.openDB( dbName);
    // create a transaction involving all tables of the database
    const tx = idb.db.transaction( idb.db.objectStoreNames, "readwrite");
    // create a list of clear invocation expressions
    const clearInvocationExpressions =
        Array.from( idb.db.objectStoreNames, osName => tx.objectStore( osName).clear());
    // invoke all of them in parallel and wait for their completion
    await Promise.all( clearInvocationExpressions);
    // wait for the completion of the transaction tx
    await tx.done;
  };

We also define a createTestData method:

  async function createTestData () {
   await idb.addRecords( "books", [
     {id: "006251587X", title: "Weaving the Web", year: 2000, edition: 2},
     {id: "0465026567", title: "Gödel, Escher, Bach", year: 1999},
     {id: "0465030793", title: "I Am a Strange Loop", year: 2008}
   ]);
  }

Finally, we define an immediately invoked function expression (IIFE) for creating an empty database with name "test" having an empty table "books" and HTML buttons for invoking the createTestData method and for invoking the clearDB method:

  (async function () {
   await idb.createEmptyDB("test",["books"]);
  })();
  </script>
</head>
<body>
 <h1>Working with IndexedDB</h1>
 <h2>Creating and Clearing a Database</h2>
  <ul>
    <li><button type="button" onclick="createTestData()">Generate</button> test data</li>
    <li><button type="button" onclick="idb.clearDB()">Clear</button> database tables</li>
  </ul>
</body>
</html>

Now it's time to try this out. Copy and paste all 5 HTML/JS code fragments from this article to a text editor and save it as an HTML file. Then, open it in a browser that allows accessing IndexedDB databases via file URLs (see the note below). Otherwise, open it via a (possibly local) web server. You can inspect the existence and contents of IndexedDB databases with the help your browser's developer tools (e.g. F12 or with Shift-Ctrl-I). Try out the following steps:

  1. Check if an IndexedDB database with name "testDB" and an empty "books" table has been created.
  2. Press the button.
  3. Then check if the "books" table has been populated with 3 book records.
  4. Press the button again and observe a constraint validation error (in the console) due to an attempt to create another record with the same ID as an already stored one.
  5. Press the button.
  6. Then check if the "books" table has been cleared.
  7. Now you could create the test data again.

Attention

While you can directly run IndexedDB JS code from a (local or remote) website (e.g., from a GitHub Pages website), you can only run it from your local file system after changing your browser's default configuration.

For FireFox, you have to set the configuration property security.fileuri.strict_origin_policy to false by first entering about:config in the browser's web address bar and then entering this property name.

Category: