Submitted by gwagner on
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:
- Check if an IndexedDB database with name "testDB" and an empty "books" table has been created.
- Press the button.
- Then check if the "books" table has been populated with 3 book records.
- 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.
- Press the button.
- Then check if the "books" table has been cleared.
- 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 enteringabout:config
in the browser's web address bar and then entering this property name.