IndexedDB is a low-level API for storing structured data in the browser. It allows you to store large amounts of data and retrieve it efficiently using key-value pairs.
Unlike localStorage, IndexedDB is asynchronous and supports advanced features like transactions, indexing, and searching.
In this tutorial, you’ll learn:
- What is IndexedDB and its benefits.
- Setting up and creating a database.
- Performing CRUD (Create, Read, Update, Delete) operations.
- Using indexes for efficient data retrieval.
- Practical examples with IndexedDB.
1. What is IndexedDB?
IndexedDB is:
- Asynchronous: Operates without blocking the main thread.
- Persistent: Stores data even after the browser is closed.
- Structured: Supports objects, arrays, and complex data types.
- Powerful: Offers transactions, versioning, and indexing for fast lookups.
Use Cases:
- Offline web applications.
- Storing large datasets like user preferences, chat history, or product catalogs.
- Caching API responses.
2. Setting Up IndexedDB
Example 1: Creating a Database
// Open or create a database const request = indexedDB.open("MyDatabase", 1); // Handle success request.onsuccess = function (event) { console.log("Database opened successfully:", event.target.result); }; // Handle errors request.onerror = function (event) { console.error("Database error:", event.target.error); }; // Set up the database structure request.onupgradeneeded = function (event) { const db = event.target.result; // Create an object store with a key path const store = db.createObjectStore("Users", { keyPath: "id" }); // Create indexes for quick lookups store.createIndex("name", "name", { unique: false }); store.createIndex("email", "email", { unique: true }); console.log("Object store and indexes created."); };
Explanation:
- indexedDB.open(“MyDatabase”, version): Opens or creates a database with the specified version.
- onupgradeneeded: Triggered if the database needs a new schema.
3. Performing CRUD Operations
Example 2: Adding Data to IndexedDB
const request = indexedDB.open("MyDatabase", 1); request.onsuccess = function (event) { const db = event.target.result; // Start a transaction const transaction = db.transaction("Users", "readwrite"); const store = transaction.objectStore("Users"); // Add data store.add({ id: 1, name: "John Doe", email: "john@example.com" }); store.add({ id: 2, name: "Jane Smith", email: "jane@example.com" }); transaction.oncomplete = function () { console.log("Data added successfully."); }; transaction.onerror = function (event) { console.error("Transaction error:", event.target.error); }; };
Explanation:
- transaction.objectStore(name): Accesses the specified object store.
- store.add(data): Adds data to the store.
Example 3: Reading Data from IndexedDB
const request = indexedDB.open("MyDatabase", 1); request.onsuccess = function (event) { const db = event.target.result; // Start a transaction const transaction = db.transaction("Users", "readonly"); const store = transaction.objectStore("Users"); // Get a specific item by key const getRequest = store.get(1); getRequest.onsuccess = function (event) { console.log("Retrieved data:", event.target.result); }; getRequest.onerror = function (event) { console.error("Get request error:", event.target.error); }; };
Explanation:
- store.get(key): Retrieves an item by its key.
Example 4: Updating Data in IndexedDB
const request = indexedDB.open("MyDatabase", 1); request.onsuccess = function (event) { const db = event.target.result; // Start a transaction const transaction = db.transaction("Users", "readwrite"); const store = transaction.objectStore("Users"); // Update an existing item const updateRequest = store.put({ id: 1, name: "John Doe", email: "john.doe@example.com" }); updateRequest.onsuccess = function () { console.log("Data updated successfully."); }; updateRequest.onerror = function (event) { console.error("Update error:", event.target.error); }; };
Explanation:
- store.put(data): Updates or adds data if the key already exists.
Example 5: Deleting Data from IndexedDB
const request = indexedDB.open("MyDatabase", 1); request.onsuccess = function (event) { const db = event.target.result; // Start a transaction const transaction = db.transaction("Users", "readwrite"); const store = transaction.objectStore("Users"); // Delete an item by key const deleteRequest = store.delete(1); deleteRequest.onsuccess = function () { console.log("Data deleted successfully."); }; deleteRequest.onerror = function (event) { console.error("Delete error:", event.target.error); }; };
Explanation:
- store.delete(key): Deletes an item by its key.
4. Using Indexes
Indexes allow you to query data efficiently.
Example 6: Querying Data by Index
const request = indexedDB.open("MyDatabase", 1); request.onsuccess = function (event) { const db = event.target.result; // Start a transaction const transaction = db.transaction("Users", "readonly"); const store = transaction.objectStore("Users"); const index = store.index("email"); // Get an item by index const indexRequest = index.get("jane@example.com"); indexRequest.onsuccess = function (event) { console.log("Queried data:", event.target.result); }; indexRequest.onerror = function (event) { console.error("Index query error:", event.target.error); }; };
Explanation:
- store.index(name): Accesses an index.
- index.get(value): Retrieves data based on an indexed value.
5. Deleting an Object Store
To delete an object store, update the database schema.
Example 7: Delete an Object Store
const request = indexedDB.open("MyDatabase", 2); request.onupgradeneeded = function (event) { const db = event.target.result; // Delete an existing object store if (db.objectStoreNames.contains("Users")) { db.deleteObjectStore("Users"); console.log("Object store deleted."); } };
6. Practical Application: Todo App
Example 8: A Simple Todo App with IndexedDB
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>IndexedDB Todo App</title> </head> <body> <h1>Todo App</h1> <input type="text" id="todoInput" placeholder="Enter a todo"> <button id="addTodo">Add Todo</button> <ul id="todoList"></ul> <script src="todoApp.js"></script> </body> </html>
JavaScript (todoApp.js)
const request = indexedDB.open("TodoDB", 1); request.onupgradeneeded = function (event) { const db = event.target.result; db.createObjectStore("Todos", { keyPath: "id", autoIncrement: true }); }; request.onsuccess = function (event) { const db = event.target.result; const addTodo = () => { const todoInput = document.getElementById("todoInput"); const transaction = db.transaction("Todos", "readwrite"); const store = transaction.objectStore("Todos"); store.add({ text: todoInput.value }); todoInput.value = ""; displayTodos(); }; const displayTodos = () => { const transaction = db.transaction("Todos", "readonly"); const store = transaction.objectStore("Todos"); const todoList = document.getElementById("todoList"); todoList.innerHTML = ""; store.openCursor().onsuccess = function (event) { const cursor = event.target.result; if (cursor) { const li = document.createElement("li"); li.textContent = cursor.value.text; todoList.appendChild(li); cursor.continue(); } }; }; document.getElementById("addTodo").addEventListener("click", addTodo); displayTodos(); };
7. Best Practices
- Use Transactions: Always use transactions for consistency.
- Handle Errors: Provide meaningful error messages.
- Schema Versioning: Use the version parameter to manage schema changes.
- Browser Compatibility: Test your application across different browsers.
8. Conclusion
In this tutorial, you learned:
- How to set up and use IndexedDB.
- CRUD operations and using indexes for efficient queries.
- How to apply IndexedDB in a practical example like a Todo App.
IndexedDB is a powerful tool for building offline-capable and data-intensive web applications.