# Mastering IndexedDB: A Developer’s Guide to Efficient Client-Side Storage

Data storage is a crucial part of most web applications, whether for tracking user data or managing application data. As modern web applications become faster and more robust, efficient client-side storage solutions are needed to enhance performance and development.

## Introduction to Client-Side Storage

This tutorial explores how to use IndexedDB for web application data storage, including setting it up and manipulating data using its API. By the end of this guide, you'll have a functional **to-do application** that utilizes IndexedDB.

## What is IndexedDB?

IndexedDB is a **low-level API for client-side storage**. It provides a **persistent NoSQL database** in the browser, allowing applications to store various types of data, such as:

* **Files and blobs**
    
* **Images and videos**
    
* **Structured data** (objects, lists, and arrays)
    

IndexedDB is widely used for **progressive web apps (PWAs)**, **caching**, and **gaming applications**, as it supports transactions and large-scale data storage.

### Base HTML Structure

Create a basic **to-do application** with an interface for viewing and adding tasks. Open `index.html` and add the following:

```xml
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TODO APP</title>
    <script src="index.js" defer></script>
    <link href="styles.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 structure includes:

1. A section to **display to-dos** stored in IndexedDB.
    
2. A form to **add new to-dos** to the database.
    

### Basic Styling

Open `styles.css` and add the following styles:

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

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

h1 {
    text-align: center;
}

section {
    display: flex;
    justify-content: center;
    padding: 10px;
    background: #3182d4;
}

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

.add {
    background: #ebe6e6;
}

ol {
    list-style-type: none;
}
```

---

## Initializing IndexedDB

Now, let’s create the IndexedDB database and an object store (`todo_tb`) to store our to-do items. Open `index.js` and add the following:

```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 });
});
```

### Explanation:

* **Creates** an IndexedDB database (`todo_db`).
    
* **Creates** an object store (`todo_tb`) with an auto-incrementing ID.
    
* **Adds indexes** for `title` and `desc`.
    

---

## Saving Data to IndexedDB

Next, let’s implement the function to save **to-do items** 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'));
}
```

---

## Retrieving and Displaying Data

To **display stored to-dos**, modify the IndexedDB success event listener:

```javascript
openOrCreateDB.addEventListener('success', () => {
  db = openOrCreateDB.result;
  showTodos();
});
```

Then, define `showTodos` to fetch and display stored items:

```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');
    }
  });
}
```

---

## Deleting Data

To implement **deletion**, define the `deleteItem` function:

```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'));
}
```

---

## Conclusion

IndexedDB is a powerful storage solution for modern web applications. In this tutorial, we:

* Set up an IndexedDB database
    
* Implemented **Add**, **Retrieve**, and **Delete** functionalities
    
* Built a **to-do application** using IndexedDB
    

### Drawbacks of IndexedDB

* **Complex API** compared to LocalStorage
    
* **Asynchronous operations** make debugging harder
    
* **Browser limitations** (some older versions don’t support it fully)
