A complete guide to using IndexedDB
Tags: webdev
Published: 2023-06-01 14:32:07

**Written by [Chibuike Nwachukwu](https://blog.logrocket.com/author/chibuikenwachukwu/)✏️**

Data storage is an important part of most web applications, from tracking user data to application data. With the rapid development of faster and more robust web applications, efficient client storage is needed to aid development. 

Client-side storage on the web has evolved a lot over the years, from cookies used to store user data to the advent of WebSQL (currently deprecated), which allowed developers to store data in a SQL database in the browser, and in turn allowing users familiar with SQL to build robust applications easily. 

IndexedDB is an alternative to WebSQL and provides more storage capacity than its previous counterpart. In this tutorial, we’ll explore how to use and set up IndexedDB for web application data storage and how to manipulate its data using the available API. 

You can find a link to the [public GitHub repo](https://github.com/chyke007/todo-indexeddb) that contains the project we created in this article. We’ll cover the following:

*   [What is IndexedDB?](#what-is-indexeddb)
*   [Setting up our project](#setting-up-project)
*   [Saving data to the IndexedDB database](#saving-data-indexeddb-database)
*   [Retrieving and displaying data](#retrieving-displaying-data)
*   [Deleting data from the database](#deleting-data-database)
*   [Incrementing the IndexedDB version](#incrementing-indexeddb-version)
*   [Drawbacks to using IndexedDB](#drawbacks-using-indexeddb)

Let’s get started!

## What is IndexedDB? <a name="what-is-indexeddb">

[IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB) is a low-level API for client-side storage. It is a full-blown, persistent NoSQL storage system available in the browser that allows for the storage of different types of data, such as:

*   Files or blobs
*   Images and videos
*   Structured data like objects, lists, and arrays

IndexedDB can be used in various scenarios, such as caching, PWAs, and gaming, and also supports transactions. It is developed to suit the multiple needs of web apps effectively.

## Setting up our project <a name="setting-up-project">

We won't be doing any fancy setup, as IndexedDB runs natively on the web. First, create a new directory to house the project:

```bash
 mkdir indexed-db && cd indexed-db
```

Now, we’ll create an `index.html` file to view our application and an `index.js` script file to store our application logic:

```bash
touch index.html index.js styles.css
```

## Saving data to the IndexedDB database <a name="saving-data-indexeddb-database">

To see the benefits of using this database and learn how to interact with the API, we’ll create a basic to-do application. We enable an **Add** feature to see how to save data to the database, another to view all to-dos, and a **Remove** feature to see the `GET` and `DELETE` functions of the API, respectively. 

Open the `index.html` created in the previous section and add the following code:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TODO APP</title>
    <script src="index.js" defer></script>
    <link href="style.css" rel="stylesheet">
</head>
<body>
    <h1>TODO APP</h1>
    <section>
        <aside class="view">
            <h2>TODOs</h2>
            <div class="todos">
                <ol></ol>
            </div>
        </aside>
        <aside class="add"> 
            <h2>Add Todo</h2>
            <form>
              <div>
                <label for="title">Todo title</label>
                <input id="title" type="text" required>
              </div>
              <div>
                <label for="desc">Todo description</label>
                <input id="desc" type="text" required>
              </div>
              <div>
                <button>Save</button>
              </div>
            </form>
        </aside>
    </section>
</body>
</html>
```

This creates the base structure of our web app. We do two main things here: first, we create a section to display/view all to-dos saved in the database, and second, we create a section for adding to-dos to the database. 

Let’s also add some basic styling to the application. Open the `styles.css` file and add the following:

```css
  html {
    font-family: sans-serif;
  }

  body {
    margin: 0 auto;
    max-width: 800px;
  }

  header, footer {
    background-color: blue;
    color: white;
    padding: 0 20px;
  }

  .add, .view {
    padding: 30px;
    width: 40%;
  }

  .add {
    background: #ebe6e6; 
  }
  section {
    padding: 10px;
    background: #3182d4;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: center;
  }

  h1 {
    margin: 0;
  }

  ol {
    list-style-type: none;
  }

  div {
    margin-bottom: 10px;
  }
```

The `index.js` file is the heart of the application, as it contains the logic for interacting between the app and IndexedDB. 

First, we need to create the database; then, we can initialize it by creating an object store (similar to a table in SQL), which we‘ll use to store each item detail. Open the `index.js` file and add the following logic to it:

```javascript
let db;
const openOrCreateDB = window.indexedDB.open('todo_db', 1);

openOrCreateDB.addEventListener('error', () => console.error('Error opening DB'));

openOrCreateDB.addEventListener('success', () => {
  console.log('Successfully opened DB');
  db = openOrCreateDB.result;
});

openOrCreateDB.addEventListener('upgradeneeded', init => {
  db = init.target.result;

  db.onerror = () => {
    console.error('Error loading database.');
  };

  const table = db.createObjectStore('todo_tb', { keyPath: 'id', autoIncrement:true });

  table.createIndex('title', 'title', { unique: false });
  table.createIndex('desc', 'desc', { unique: false });
});
```

As can be seen above, a database named `todo_db` is created, and then an object store named `todo_tb` is created with two indexes, `title` and `desc`. These indexes allow their values to be duplicated in the store, which is similar to creating a table in SQL and then creating two columns. 

Next, to add the **Save** functionality, we proceed to retrieve the values entered into the form and then save them to the database:

```javascript
const todos = document.querySelector('ol');
const form = document.querySelector('form');
const todoTitle = document.querySelector('#title');
const todoDesc = document.querySelector('#desc');
const submit = document.querySelector('button');

form.addEventListener('submit', addTodo);

function addTodo(e) {
  e.preventDefault();
  const newTodo = { title: todoTitle.value, body: todoDesc.value };
  const transaction = db.transaction(['todo_tb'], 'readwrite');
  const objectStore = transaction.objectStore('todo_tb');
  const query = objectStore.add(newTodo);
  query.addEventListener('success', () => {
    todoTitle.value = '';
    todoDesc.value = '';
  });
  transaction.addEventListener('complete', () => {
    showTodos();
  });
  transaction.addEventListener('error', () => console.log('Transaction error'));
}
```

After adding the values to the store, the two form fields are emptied so the user can enter a new item to their list. We can update the view by calling the `showTodos` method, which we’ll see in the next section.

## Retrieving and displaying data <a name="retrieving-displaying-data">

To confirm if the **Save** to-do function worked, open and use your browser’s **Inspect** function. In Chrome, you can see IndexedDB under the **Application** tab, in **Storage**. As can be seen in the image below, we created the database and saved the first to-do to the `todo_tb` object store: ![todo_tb shows one item](http://blog.logrocket.com/wp-content/uploads/2023/05/todo_tb-shows-one-item.png) 

To display the available to-dos when the user loads the page, and provide a view of previously added and removed to-dos, we’ll create a method called `showTodos`:

```javascript
function showTodos() {
  while (todos.firstChild) {
    todos.removeChild(todos.firstChild);
  }
  const objectStore = db.transaction('todo_tb').objectStore('todo_tb');
  objectStore.openCursor().addEventListener('success', e => {

    const pointer = e.target.result;
    if(pointer) {
      const listItem = document.createElement('li');
      const h3 = document.createElement('h3');
      const pg = document.createElement('p');
      listItem.appendChild(h3);
      listItem.appendChild(pg);
      todos.appendChild(listItem);
      h3.textContent = pointer.value.title;
      pg.textContent = pointer.value.body;
      listItem.setAttribute('data-id', pointer.value.id);
      const deleteBtn = document.createElement('button');
      listItem.appendChild(deleteBtn);
      deleteBtn.textContent = 'Remove';
      deleteBtn.addEventListener('click', deleteItem);
      pointer.continue();
    } else {
      if(!todos.firstChild) {
        const listItem = document.createElement('li');
        listItem.textContent = 'No Todo.'
        todos.appendChild(listItem);
      }

      console.log('Todos all shown');
    }
  });
}
```

This method gets the to-dos from the store, loops through each item, and creates an HTML element for each. It appends the item to the `ol` list element on the webpage and passes the `id` of each to-do into a data attribute called `data-id`. We’ll use this unique ID later, when we cover the `deleteItem` function, to identify each to-do when we need to remove it from the store. 

To fetch the to-dos on page load, modify the `openOrCreateDB` success event listener to this:

```javascript
openOrCreateDB.addEventListener('success', () => {
  console.log('Successfully opened DB');
  db = openOrCreateDB.result;
  showTodos();
});
```

## Deleting data from the database <a name="deleting-data-database">

Finally, let’s test the `DELETE` API for this database and create a **Delete** function for our to-do list app:

```javascript
function deleteItem(e) {
  const todoId = Number(e.target.parentNode.getAttribute('data-id'));
  const transaction = db.transaction(['todo_tb'], 'readwrite');
  const objectStore = transaction.objectStore('todo_tb');
  objectStore.delete(todoId);
  transaction.addEventListener('complete', () => {
    e.target.parentNode.parentNode.removeChild(e.target.parentNode);
    alert(`Todo with id of ${todoId} deleted`)
    console.log(`Todo:${todoId} deleted.`);
    if(!todos.firstChild) {
      const listItem = document.createElement('li');
      listItem.textContent = 'No Todo.';
      todos.appendChild(listItem);
    }
  });
  transaction.addEventListener('error', () => console.log('Transaction error'));
}
```

This deletes the particular to-do using the unique ID passed to the method and removes the element from the webpage. Once it deletes the last to-do item in the store, it shows a “no to-dos” message in the place of the to-do list. 

To confirm that the to-do has been deleted from the database, proceed to inspect the webpage and click the **Application** tab. As can be seen, the `todo_tb` object store now contains no items: ![todo_tb is empty](http://blog.logrocket.com/wp-content/uploads/2023/05/todo_tb-empty.png) The final web application looks like this: ![The final app view](http://blog.logrocket.com/wp-content/uploads/2023/05/final-app-view.png)

## Incrementing the IndexedDB version <a name="incrementing-indexeddb-version">

IndexedDB also allows developers to increment the database version. When you open the database, specify your desired version number:

```javascript
window.indexedDB.open('todo_db', 1);
```

If the database doesn't exist, it will be created with the specified version. If the database already exists, the version number is checked. 

If the version number specified during the open method call is higher than the existing version, a version change event is triggered via the `onUpgradeNeeded` event. This event allows you to perform database schema changes or data migrations. 

A point to note here is deleting a previous object store to add new options when creating a new store would also delete all other data in the old store. Take care to read the old content out and save it somewhere else before upgrading the database.

## Drawbacks of using IndexedDB <a name="drawbacks-using-indexeddb">

Since IndexedDB relies on the client's web browser, it is typically more suitable for individual users or smaller-scale applications. Though it can handle a significant amount of data, there are certain considerations to keep in mind when using IndexedDB in large-scale apps or apps used by multiple people.

### Scalability limitations

IndexedDB operates within a web browser, which means it is limited to the capabilities and resources of the client-side environment. It may not scale well for scenarios that require handling large numbers of simultaneous users or extremely high throughput.

### Data size limitations

Different web browsers impose limits on the maximum amount of data that can be stored in IndexedDB. These limits vary across browsers and can range from a few megabytes to several hundred megabytes. 

It is greatly important to be aware of these limitations and design the application accordingly. Once your data storage runs out, you won't be able to store new data in the database, as you would trigger a `QuotaExceededError` error.

### Synchronization challenges

IndexedDB doesn’t provide inbuilt mechanisms for handling data synchronization between clients or handling conflicts in a distributed environment. You would need to implement custom synchronization logic to handle these scenarios. Maintaining data consistency and synchronization across different instances of the application becomes complex. 

Hence, for larger-scale applications or applications used by multiple people, it is more efficient to use server-side databases or cloud-based storage solutions.

## Conclusion

In this article, we learned about IndexedDB, a database on the web, and how to interact with it to store web application data using JavaScript. 

Hopefully, you enjoyed this article and have learned a new way of managing your application data locally on the web. Thanks for reading!