Progressive Web Apps: IndexedDB

1. Welcome

In this lab, you'll back up and recover client data to IndexedDB. This is the third in a series of companion codelabs for the Progressive Web App workshop. The previous codelab was Working with Workbox. There are five more codelabs in this series.

What you'll learn

  • Create an IndexedDB database and object store using idb
  • Add and retrieve items to an object store

What you should know

  • JavaScript and Promises

What you will need

2. Get Set Up

Start by either cloning or downloading the starter code needed to complete this codelab:

If you clone the repo, make sure you're on the pwa03--indexeddb branch. The zip file contains the code for that branch, too.

This codebase requires Node.js 14 or higher. Once you have the code available, run npm ci from the command line in the code's folder in order to install all of the dependencies you'll need. Then, run npm start to start the development server for the codelab.

The source code's README.md file provides an explanation for all distributed files. In addition, the following are the key existing files you'll be working with throughout this codelab:

Key Files

  • js/main.js - Main application JavaScript file

3. Set Up Database

Before an IndexedDB database can be used, it needs to be opened and set up. While you can do this directly, because IndexedDB was standardized before Promises were prominent, it's callback based interface can be unwieldy to use. Instead, we'll be using idb, a very small Promise wrapper for IndexedDB. To start, first import it into js/main.js:

import { openDB } from 'idb';

Then, add the following setup code to the top of the DOMContentLoaded event listener:

// Set up the database
const db = await openDB('settings-store', 1, {
  upgrade(db) {
    db.createObjectStore('settings');
  },
});

Explanation

Here, an IndexedDB database called settings-store is created. Its version is initialized to 1 and its initialized with an object store called settings. This is the most basic kind of object store, simple key-value pairs, but more complex object stores can be created as needed. Without this initialization of an object store, there will be nowhere to put data in, so leaving this out here would be like creating a database with no tables.

4. Save Editor State on Update

With the database initialized, it's time to save content to it! The editor exposes an onUpdate method that lets you pass a function to be called whenever content gets updated in the editor. It's the perfect place to tap in and add the changes to the database. To do so, add the following code right before the defaultText declaration in js/main.js:

// Save content to database on edit
editor.onUpdate(async (content) => {
  await db.put('settings', content, 'content');
});

Explanation

db is the previously opened IndexedDB database. The put method allows entries in an object store in that database to be created or updated. The first argument is the object store in the database to use, the second argument is the value to store, and the third argument is the key to save the value to if it's not clear from the value (in this case it's not as our database doesn't include specified keys). Because it's asynchronous, it's wrapped in async/await.

5. Retrieve State on Load

Finally, in order to recover the user's in-progress work, it needs to be loaded when the editor loads. The editor supplies a setContent method to do just that, set it's content. It's currently used to set it to the value of defaultText. Update it with the following to load the user's previous work in instead:

editor.setContent((await db.get('settings', 'content')) || defaultText);

Explanation

Instead of just setting the editor to the value of defaultText, it now attempts to get the content key from the settings object store in the settings-store IndexedDB database. If that value exists,, that's used. If not, the default text is used.

6. Set and Retrieve Night Mode State

Now that you're comfortable with IndexedDB, add the following code to the bottom of js/main.js and update it to save the user's night mode preference when it changes, and load that preference when night mode initializes.

// Set up night mode toggle
const { NightMode } = await import('./app/night-mode.js');
new NightMode(
  document.querySelector('#mode'),
  async (mode) => {
    editor.setTheme(mode);
    // Save the night mode setting when changed
  },
  // Retrieve the night mode setting on initialization
);

7. Congratulations!

You've learned how to save and load data from an object store in IndexedDB.

The next codelab in the series is From Tab to Taskbar