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:
<!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:
A section to display to-dos stored in IndexedDB.
A form to add new to-dos to the database.
Basic Styling
Open styles.css
and add the following styles:
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:
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
anddesc
.
Saving Data to IndexedDB
Next, let’s implement the function to save to-do items to the database.
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:
openOrCreateDB.addEventListener('success', () => {
db = openOrCreateDB.result;
showTodos();
});
Then, define showTodos
to fetch and display stored items:
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:
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)
Subscribe to my newsletter
Read articles from Nazrul Islam directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Nazrul Islam
Nazrul Islam
💻 Software Engineer 🧑💻 Full Stack Developer 🗺️ Based in India 📧 contact@nazrulislam.dev 📧 nazrul@stacknothing.com