Apidog

All-in-one Collaborative API Development Platform

API Design

API Documentation

API Debugging

API Mocking

API Automated Testing

A Beginner's Guide to Use IndexedDB APIs

This tutorial covers the essentials of IndexedDB, its features, and how to integrate it into your web projects.

Emmanuel Mumba

Emmanuel Mumba

Updated on March 27, 2025

Building applications that work seamlessly offline has become a key requirement in modern web development. IndexedDB offers a powerful solution for developers looking to store and manage large amounts of data directly in the browser. This client-side NoSQL database allows for efficient data handling, making it ideal for web applications that need offline functionality. In this article, we’ll explore the basics of IndexedDB, its key features, and walk you through the steps to set it up and use it in your web projects.

What is IndexedDB?

IndexedDB is a powerful client-side NoSQL database that enables web applications to store and manage structured data directly in the browser. It provides an efficient solution for handling large datasets locally, making it ideal for offline functionality and data-heavy applications.

One of the main advantages of IndexedDB is its ability to support offline web applications. It allows developers to store substantial amounts of data, use indexing for fast retrieval, manage transactions, and navigate data using cursors. These features make it an excellent choice for handling complex data in web apps.

Most modern web browsers, including Chrome, Firefox, Safari, and Edge, support IndexedDB. However, it’s important to ensure that the version of IndexedDB is compatible with the browser you’re targeting for your web application.

To begin using IndexedDB, the first step is to open a connection to the database, which is necessary to create and interact with it.

💡
when you're also working with APIs, it's important to consider how tools like Apidog can streamline your workflow. Apidog provides a unified platform that simplifies API design, testing, and documentation. Unlike other tools that require multiple separate steps for API management, Apidog brings everything into one place with features like real-time syncing and seamless collaboration for development teams.
button

Getting Started with IndexedDB: A Comprehensive Guide

IndexedDB is a powerful client-side storage solution for web applications. This guide will walk you through everything from basic setup to advanced techniques and real-world implementation.

Setting Up IndexedDB

Creating and Opening a Database

To create a new database in IndexedDB, you'll use the indexedDB.open() method:

const request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // Create object stores and indexes here
};

The method takes two parameters: the database name and version number. To open an existing database, you can simply call the method without specifying a version number.

Database Versioning

IndexedDB supports versioning to handle schema changes. When you open a database with a higher version number than the existing one, the onupgradeneeded event is triggered, allowing you to update your database schema.

Creating Object Stores and Indexes

Object stores are containers for your data in IndexedDB:

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  // Create an object store with a key path
  const objectStore = db.createObjectStore("customers", { keyPath: "id" });
  
  // Create an index for efficient querying
  objectStore.createIndex("name", "name", { unique: false });
};

Working with Transactions

Understanding Transaction Basics

Transactions in IndexedDB maintain data integrity by grouping multiple operations into a single, atomic unit. They ensure that either all changes are applied or none are.

Creating and Managing Transactions

const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");

transaction.oncomplete = (event) => {
  console.log("Transaction completed successfully");
};

transaction.onerror = (event) => {
  console.error("Transaction failed");
};

Error Handling and Rolling Back

If an operation fails, you can use the abort() method to roll back the entire transaction:

try {
  // Perform operations
} catch (error) {
  transaction.abort();
  console.error("Transaction aborted:", error);
}

Data Operations in IndexedDB

Adding Data

const customerData = { id: "00001", name: "John Doe", email: "john@example.com" };
const request = objectStore.add(customerData);

request.onsuccess = (event) => {
  console.log("Data added successfully");
};

Retrieving Data

Basic data retrieval by key:

const request = objectStore.get("00001");
request.onsuccess = (event) => {
  console.log("Customer data:", request.result);
};

Filtering with Indexes

const index = objectStore.index("name");
const request = index.get("John Doe");

request.onsuccess = (event) => {
  console.log("Found by name:", request.result);
};

Advanced Querying Methods

For complex queries, IndexedDB offers range queries and compound queries using methods like openCursor() and IDBKeyRange:

const range = IDBKeyRange.bound("A", "F"); // Names starting with A through F
const request = index.openCursor(range);

Updating Records

const updateData = { id: "00001", name: "John Smith", email: "john@example.com" };
const request = objectStore.put(updateData);

Deleting Records

const request = objectStore.delete("00001");
request.onsuccess = (event) => {
  console.log("Record deleted");
};

Working with Cursors

Understanding Cursor Functionality

Cursors allow you to efficiently iterate over records in an object store or index, providing a way to traverse and manipulate data.

const request = objectStore.openCursor();
request.onsuccess = (event) => {
  const cursor = event.target.result;
  if (cursor) {
    console.log("Key:", cursor.key, "Value:", cursor.value);
    cursor.continue(); // Move to the next record
  } else {
    console.log("No more records");
  }
};

Modifying Data Using Cursors

const transaction = db.transaction(["customers"], "readwrite");
const objectStore = transaction.objectStore("customers");
const request = objectStore.openCursor();

request.onsuccess = (event) => {
  const cursor = event.target.result;
  if (cursor) {
    if (cursor.value.status === "inactive") {
      const updateData = cursor.value;
      updateData.status = "active";
      cursor.update(updateData);
    }
    cursor.continue();
  }
};

Schema Management and Upgrades

Upgrading Database Schema

When your application evolves, you may need to modify your database schema:

const request = indexedDB.open("MyDatabase", 2); // Increase version number

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  
  // Check if the object store exists
  if (!db.objectStoreNames.contains("newStore")) {
    db.createObjectStore("newStore", { keyPath: "id" });
  }
};

Migrating Data During Upgrades

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  const oldVersion = event.oldVersion;
  
  if (oldVersion < 1) {
    // First version setup
  }
  
  if (oldVersion < 2) {
    // Migrate data to new schema
    const transaction = event.target.transaction;
    const oldStore = transaction.objectStore("oldStore");
    const newStore = db.createObjectStore("newStore", { keyPath: "id" });
    
    oldStore.openCursor().onsuccess = (event) => {
      const cursor = event.target.result;
      if (cursor) {
        newStore.add(cursor.value);
        cursor.continue();
      }
    };
  }
};

Performance Optimization

Efficient Bulk Operations

For better performance, use bulk operations when dealing with multiple records:

const transaction = db.transaction(["customers"], "readwrite");
const store = transaction.objectStore("customers");

// Add multiple records in a single transaction
customerList.forEach(customer => {
  store.add(customer);
});

Leveraging Indexes for Faster Queries

Create indexes on frequently queried properties to ensure faster data retrieval:

objectStore.createIndex("email", "email", { unique: true });
objectStore.createIndex("lastLogin", "lastLogin", { unique: false });

Connection Management Best Practices

Open connections only when necessary and close them when done:

let db;

function openDB() {
  const request = indexedDB.open("MyDatabase", 1);
  request.onsuccess = (event) => {
    db = event.target.result;
  };
  return request;
}

// When you're done with the database
function closeDB() {
  if (db) {
    db.close();
    db = null;
  }
}

Real-World Example: Task Manager with Offline Support

Database Setup

const request = indexedDB.open("TaskManagerDB", 1);

request.onupgradeneeded = (event) => {
  const db = event.target.result;
  const taskStore = db.createObjectStore("tasks", { keyPath: "id", autoIncrement: true });
  
  // Create indexes for querying
  taskStore.createIndex("status", "status", { unique: false });
  taskStore.createIndex("dueDate", "dueDate", { unique: false });
};

Adding Tasks

function addTask(taskData) {
  const transaction = db.transaction(["tasks"], "readwrite");
  const taskStore = transaction.objectStore("tasks");
  
  return new Promise((resolve, reject) => {
    const request = taskStore.add({
      title: taskData.title,
      description: taskData.description,
      status: "pending",
      dueDate: taskData.dueDate,
      createdAt: new Date()
    });
    
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

Retrieving and Displaying Tasks

function getAllTasks() {
  const transaction = db.transaction(["tasks"], "readonly");
  const taskStore = transaction.objectStore("tasks");
  
  return new Promise((resolve, reject) => {
    const request = taskStore.openCursor();
    const tasks = [];
    
    request.onsuccess = (event) => {
      const cursor = event.target.result;
      if (cursor) {
        tasks.push(cursor.value);
        cursor.continue();
      } else {
        resolve(tasks);
      }
    };
    
    request.onerror = () => reject(request.error);
  });
}

Updating and Deleting Tasks

function updateTaskStatus(id, newStatus) {
  const transaction = db.transaction(["tasks"], "readwrite");
  const taskStore = transaction.objectStore("tasks");
  
  return new Promise((resolve, reject) => {
    const getRequest = taskStore.get(id);
    
    getRequest.onsuccess = () => {
      const task = getRequest.result;
      task.status = newStatus;
      task.updatedAt = new Date();
      
      const updateRequest = taskStore.put(task);
      updateRequest.onsuccess = () => resolve(true);
      updateRequest.onerror = () => reject(updateRequest.error);
    };
    
    getRequest.onerror = () => reject(getRequest.error);
  });
}

Syncing with the Server

function syncWithServer() {
  if (!navigator.onLine) {
    return Promise.reject(new Error("No internet connection"));
  }
  
  return getAllTasks()
    .then(tasks => {
      // Filter tasks that need syncing
      const unsynced = tasks.filter(task => !task.synced);
      
      // Send to server using fetch API
      return fetch('https://api.example.com/tasks/sync', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(unsynced)
      });
    })
    .then(response => response.json())
    .then(result => {
      // Mark tasks as synced
      const transaction = db.transaction(["tasks"], "readwrite");
      const taskStore = transaction.objectStore("tasks");
      
      result.syncedIds.forEach(id => {
        const request = taskStore.get(id);
        request.onsuccess = () => {
          const task = request.result;
          task.synced = true;
          taskStore.put(task);
        };
      });
      
      return result;
    });
}

// Listen for online events to sync automatically
window.addEventListener('online', syncWithServer);

Integrating Apidog for API Management with IndexedDB

When building applications with IndexedDB for client-side storage, you'll often need to interact with back-end APIs for data synchronization. Apidog provides a seamless solution for managing these API interactions.

Why Apidog Enhances IndexedDB Development

As you develop offline-capable applications with IndexedDB, Apidog offers several benefits:

Real-Time Synchronization: Apidog ensures your API endpoints used for data syncing are always properly configured and tested, eliminating integration issues when your app goes online.

Mock API Responses: When developing offline functionality, Apidog's smart mock engines allow you to simulate API responses for testing your IndexedDB sync logic without a live server.

Collaborative API Design: When your team is simultaneously working on front-end storage and back-end APIs, Apidog facilitates real-time collaboration on API specifications.

By integrating Apidog into your development workflow, you create a seamless bridge between client-side storage and server-side processing, making your application more robust and easier to maintain.

button

How to Build MCP Servers with the OpenAI Agents SDKTutorials

How to Build MCP Servers with the OpenAI Agents SDK

Learn how to build MCP Servers with OpenAI Agents SDK in this detailed technical guide Follow step-by-step instructions, explore 5 practical examples, and optimize your AI applications for seamless integration Ideal for developers aiming to enhance AI interoperability with MCP Servers

Ashley Innocent

March 28, 2025

How to Develop a REST API? Tools and Detailed GuideTutorials

How to Develop a REST API? Tools and Detailed Guide

This guide introduces to guide you to how to develop REST API and recommends some tools such as Postman, Swagger, Apidog, etc.

David Demir

March 27, 2025

How to Add Cline Memory Bank in Cursor: Step-by-Step GuideTutorials

How to Add Cline Memory Bank in Cursor: Step-by-Step Guide

This guide explains how to enable and configure the feature, helping you maintain project continuity and efficiency.

Emmanuel Mumba

March 27, 2025