IndexedDB Guide: JavaScript Database Transactions
IndexedDB is a powerful, low-level client-side storage API built into modern web browsers, designed for handling large amounts of structured data, including files and blobs. This article explains the core concepts of IndexedDB and details step-by-step how JavaScript uses transactional database operations to guarantee reliable, atomic, and safe data management within the browser.
What is IndexedDB?
IndexedDB is an asynchronous, transactional, object-oriented database
embedded directly within the browser. Unlike localStorage,
which is synchronous, limited to 5MB, and restricted to simple strings,
IndexedDB offers significant advantages:
- High Capacity: Allows applications to store hundreds of megabytes or more, depending on available disk space and browser quotas.
- Structured Data: Stores complex JavaScript objects, arrays, and binary data without requiring manual serialization.
- Indexes and Queries: Supports indexes to enable fast lookups across any property, not just primary keys.
- Asynchronous Execution: Prevents UI blocking by executing operations in the background.
Understanding Transactions in IndexedDB
Every read or write operation in IndexedDB must occur within the scope of a transaction. Transactions ensure data integrity through the ACID principles (Atomicity, Consistency, Isolation, Durability).
If an operation inside a transaction fails, all changes made within that transaction are rolled back automatically, preventing partial or corrupted data states.
Transaction Modes
JavaScript supports three primary transaction modes:
readonly: Permits read operations only. Multiplereadonlytransactions can access the same object store simultaneously.readwrite: Permits reading, adding, updating, and deleting data. Only onereadwritetransaction can modify an object store at a given time.versionchange: A special transaction used exclusively during database creation or structural schema upgrades (creating/deleting object stores or indexes).
How JavaScript Executes Transactional Operations
Performing database operations in JavaScript follows a distinct lifecycle: opening a connection, defining the schema, creating a transaction, and executing database requests.
1. Opening the Database and Schema Setup
A database connection is initiated with
indexedDB.open(). Schema changes, such as creating object
stores, occur inside the onupgradeneeded event handler.
const request = indexedDB.open("AppDatabase", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains("users")) {
db.createObjectStore("users", { keyPath: "id" });
}
};2. Creating a Transaction and Accessing the Object Store
Once the database is open (onsuccess), you create a
transaction by specifying target object stores and the access mode, then
retrieve the object store reference.
request.onsuccess = (event) => {
const db = event.target.result;
// 1. Create a transaction
const transaction = db.transaction(["users"], "readwrite");
// 2. Access the object store
const store = transaction.objectStore("users");
// 3. Perform the operation
const addRequest = store.add({ id: 101, name: "Jane Doe", email: "jane@example.com" });
addRequest.onsuccess = () => {
console.log("Record added successfully.");
};
// 4. Handle transaction lifecycle events
transaction.oncomplete = () => {
console.log("Transaction completed successfully.");
};
transaction.onerror = (err) => {
console.error("Transaction failed:", err.target.error);
};
};3. Reading and Modifying Data
Common data operations use specific methods available on the object store within a transaction:
store.get(key): Retrieves a record by primary key.store.put(object): Inserts or updates an existing record.store.delete(key): Removes a record by primary key.store.clear(): Deletes all records from the object store.
4. Transaction Lifecycle and Auto-Commit
In JavaScript, IndexedDB transactions commit automatically when all
queued requests complete and the active event loop tick finishes without
new requests being added. If an explicit cancellation is needed,
invoking transaction.abort() instantly rolls back all
pending and completed changes within that transaction scope.