IndexedDB
This lesson explains IndexedDB with clear examples, use cases, and best practices so you can use HTML5 APIs confidently in real web applications.
HTML5 IndexedDB API Overview
The HTML5 IndexedDB API is a browser-based database API used to store
large amounts of structured data on the client side. Unlike Local Storage,
IndexedDB supports objects, indexes, transactions, large datasets, offline
storage, and asynchronous database operations.
IndexedDB is commonly used in Progressive Web Apps, offline-first
applications, note apps, dashboards, e-commerce carts, form drafts,
local caches, document editors, media applications, and apps that need to
work without a constant internet connection.
| Feature | Description |
| API Name | IndexedDB API |
| Main Object | indexedDB |
| Storage Type | Client-side NoSQL object database |
| Data Format | JavaScript objects, files, blobs, arrays, and structured data |
| Programming Style | Asynchronous event-based API |
| Best For | Large offline data, caching, search indexes, drafts, PWA data |
IndexedDB Core Concepts
IndexedDB uses databases, object stores, keys, indexes, and transactions
to organize and manage data inside the browser.
| Concept | Description | Example |
| Database | Container for one or more object stores. | AppDatabase |
| Object Store | Similar to a table that stores records. | users, products, drafts |
| Record | A stored JavaScript object. | { id: 1, name: "Ayaan" } |
| Key | Unique identifier for a record. | id |
| Index | Searchable field inside an object store. | email, category |
| Transaction | Controls read or write operations. | readonly, readwrite |
Open IndexedDB Database
// Open IndexedDB database
const request =
indexedDB.open("AppDatabase", 1);
request.onupgradeneeded = (event) => {
const database =
event.target.result;
if (!database.objectStoreNames.contains("users")) {
database.createObjectStore(
"users",
{
keyPath: "id",
autoIncrement: true
}
);
}
};
request.onsuccess = (event) => {
const database =
event.target.result;
console.log("Database opened:", database.name);
};
request.onerror = () => {
console.error("Database failed to open.");
};
The indexedDB.open() method opens a database. The
onupgradeneeded event runs when the database is created for
the first time or when the version number increases.
IndexedDB Methods and Events
| Method / Event | Purpose | Example Syntax |
indexedDB.open() | Opens or creates a database. | indexedDB.open("AppDatabase", 1) |
onupgradeneeded | Creates or updates schema. | request.onupgradeneeded = callback |
onsuccess | Runs when request succeeds. | request.onsuccess = callback |
onerror | Runs when request fails. | request.onerror = callback |
transaction() | Creates a transaction. | database.transaction("users", "readwrite") |
objectStore() | Accesses an object store. | transaction.objectStore("users") |
add() | Adds a new record. | store.add(user) |
put() | Adds or updates a record. | store.put(user) |
get() | Reads one record by key. | store.get(1) |
getAll() | Reads all records. | store.getAll() |
delete() | Deletes one record. | store.delete(1) |
clear() | Deletes all records from a store. | store.clear() |
Add Data to IndexedDB
// Add user record
function addUser(database, user) {
const transaction =
database.transaction("users", "readwrite");
const store =
transaction.objectStore("users");
const request =
store.add(user);
request.onsuccess = () => {
console.log("User added successfully.");
};
request.onerror = () => {
console.error("Failed to add user.");
};
}
addUser(
database,
{
name: "Ayaan",
email: "ayaan@example.com",
role: "Student"
}
);
The add() method inserts a new record. If a record with the
same key already exists, the request fails.
Read Data from IndexedDB
// Read one user by key
function getUser(database, id) {
const transaction =
database.transaction("users", "readonly");
const store =
transaction.objectStore("users");
const request =
store.get(id);
request.onsuccess = () => {
console.log("User:", request.result);
};
request.onerror = () => {
console.error("Failed to read user.");
};
}
getUser(database, 1);
Use get() to read a single record by key. Use
getAll() when you need all records from an object store.
Read All Records
// Read all users
function getAllUsers(database) {
const transaction =
database.transaction("users", "readonly");
const store =
transaction.objectStore("users");
const request =
store.getAll();
request.onsuccess = () => {
console.log("Users:", request.result);
};
}
getAllUsers(database);
The getAll() method returns all records from the selected
object store.
Update Data with put()
// Update user record
function updateUser(database, user) {
const transaction =
database.transaction("users", "readwrite");
const store =
transaction.objectStore("users");
const request =
store.put(user);
request.onsuccess = () => {
console.log("User updated.");
};
}
updateUser(
database,
{
id: 1,
name: "Ayaan Poluru",
email: "ayaan@example.com",
role: "Student"
}
);
The put() method updates an existing record or creates it if
it does not already exist.
Delete Data from IndexedDB
// Delete user record
function deleteUser(database, id) {
const transaction =
database.transaction("users", "readwrite");
const store =
transaction.objectStore("users");
const request =
store.delete(id);
request.onsuccess = () => {
console.log("User deleted.");
};
}
deleteUser(database, 1);
Use delete() to remove one record by key. Use
clear() to remove every record from an object store.
Create and Use Indexes
// Create index during upgrade
request.onupgradeneeded = (event) => {
const database =
event.target.result;
const store =
database.createObjectStore(
"users",
{
keyPath: "id",
autoIncrement: true
}
);
store.createIndex(
"email",
"email",
{
unique: true
}
);
};
// Search by indexed email
function getUserByEmail(database, email) {
const transaction =
database.transaction("users", "readonly");
const store =
transaction.objectStore("users");
const index =
store.index("email");
const request =
index.get(email);
request.onsuccess = () => {
console.log(request.result);
};
}
getUserByEmail(database, "ayaan@example.com");
Indexes allow fast lookup by fields other than the primary key, such as
email, category, status, or created date.
IndexedDB Transactions
| Transaction Mode | Description | Best Use |
readonly | Allows read-only operations. | Search, display, read records. |
readwrite | Allows reading and writing. | Add, update, delete records. |
versionchange | Used during schema upgrades. | Create object stores and indexes. |
Offline Cache Example
// Save API response to IndexedDB
async function cacheUsers(database) {
const response =
await fetch("/api/users");
const users =
await response.json();
const transaction =
database.transaction("users", "readwrite");
const store =
transaction.objectStore("users");
users.forEach((user) => {
store.put(user);
});
}
This example fetches users from an API and stores them locally so the app
can display cached data later when offline.
Common IndexedDB Use Cases
| Use Case | How IndexedDB Helps |
| Progressive Web Apps | Stores app data locally for offline access. |
| Offline Forms | Saves form drafts until network is available. |
| E-commerce Carts | Keeps cart items available across sessions. |
| Media Apps | Stores images, audio, videos, and Blob data. |
| Dashboards | Caches large API datasets for faster loading. |
| Note Apps | Saves notes locally before syncing to the server. |
| Search Indexes | Stores indexed content for local search. |
| Document Editors | Stores drafts, autosaves, and local document versions. |
IndexedDB vs Local Storage vs Cookies
| Feature | IndexedDB | Local Storage | Cookies |
| Storage Size | Large | Small to medium | Very small |
| Data Type | Objects, files, blobs, structured data | String only | String only |
| Async | ✔ Yes | ✘ No | ✘ No |
| Indexes | ✔ Yes | ✘ No | ✘ No |
| Sent to Server | ✘ No | ✘ No | ✔ Yes |
| Best Use | Offline app data | Simple preferences | Sessions and authentication |
IndexedDB Best Practices
- Use IndexedDB for large structured client-side data.
- Create object stores and indexes only inside
onupgradeneeded. - Use
readonly transactions when only reading data. - Use
readwrite transactions only when modifying data. - Keep records small and avoid unnecessary duplication.
- Handle all request errors with
onerror. - Version your database carefully when changing schema.
- Do not store sensitive data without encryption and a clear security plan.
Common IndexedDB Mistakes
- Trying to use IndexedDB like synchronous Local Storage.
- Creating object stores outside
onupgradeneeded. - Forgetting to increase the database version after schema changes.
- Ignoring request and transaction errors.
- Using IndexedDB for tiny values that Local Storage can handle.
- Storing sensitive data without encryption.
- Using too many object stores without planning the schema.
- Not providing offline sync conflict handling.
Key Takeaways
- IndexedDB is a client-side database for large structured data.
- Use
indexedDB.open() to create or open a database. - Use object stores to save records.
- Use transactions for reading and writing data safely.
- Use indexes for efficient search by specific fields.
- IndexedDB is ideal for offline-first and Progressive Web Apps.
Pro Tip
IndexedDB is powerful but verbose. In production applications, many teams
use helper libraries such as Dexie.js or idb to simplify database
operations while still using IndexedDB under the hood.