Skip to content

IndexedDB - Object Stores

Object Stores are the core data containers in an IndexedDB database, analogous to ‘tables’ in a traditional SQL database. A single database can contain multiple object stores, each designed to hold a specific type of JavaScript object. This chapter details how to create, define keys for, and manage object stores.

Object stores can only be created or modified within the onupgradeneeded event handler. This design ensures that all schema modifications happen in a controlled, versioned manner. You use the IDBDatabase.createObjectStore() method to create a new store.

db.createObjectStore(storeName, options);
  • storeName: A string that names the object store.
  • options: An optional object to configure the primary key. The two main properties are keyPath and autoIncrement.

Every record in an object store must have a unique key to identify it. IndexedDB offers two main ways to manage these primary keys:

  • In-line Keys (Key Path): The key is a property of the object being stored. You specify this property using the keyPath option. For example, if you store user objects, you might use an email or userId property as the key path: { keyPath: 'userId' }.
  • Out-of-line Keys (Key Generator): The key is generated separately and stored alongside the value. By setting the autoIncrement option to true, you can have IndexedDB automatically generate an increasing integer key for each new object. This is useful when your objects don’t have a naturally unique property.

You can use both keyPath and autoIncrement together. This is useful for assigning a unique, auto-incrementing ID to a specific property of your objects.

Example: Creating an Object Store with a Key Path

Section titled “Example: Creating an Object Store with a Key Path”
const request = indexedDB.open("CustomerDB", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create a 'customers' store where the 'email' property of each object is the primary key.
const store = db.createObjectStore("customers", { keyPath: "email" });
console.log("'customers' object store created successfully.");
};

Example: Creating an Object Store with an Auto-Incrementing Key

Section titled “Example: Creating an Object Store with an Auto-Incrementing Key”
const request = indexedDB.open("TaskDB", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create a 'tasks' store with an auto-incrementing integer key.
// The key will be stored separately from the task object.
const store = db.createObjectStore("tasks", { autoIncrement: true });
// We can also combine them.
// This will create a 'logs' store where each log object gets an 'id' property
// that is automatically assigned an incrementing number.
const logStore = db.createObjectStore("logs", { keyPath: 'id', autoIncrement: true });
console.log("'tasks' and 'logs' object stores created.");
};

While a primary key allows you to retrieve a single record efficiently, indexes enable fast lookups based on other properties of your objects. An index is like a separate, sorted view of your data. You create indexes on an object store instance using the createIndex() method, also within onupgradeneeded.

objectStore.createIndex(indexName, keyPath, options);
  • indexName: A name for the index.
  • keyPath: The property of the stored object that the index should use for sorting and lookups. This can also be an array for compound indexes.
  • options: An optional object with configuration properties, such as unique (ensures all values in the index are unique) and multiEntry (for indexing array values).

Example: Creating Indexes and Seeding Data

Section titled “Example: Creating Indexes and Seeding Data”

This example sets up a products store with indexes on category and price. It also shows how to add initial data right after the schema is created.

const request = indexedDB.open("StoreDB", 1);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Create the object store
const store = db.createObjectStore("products", { keyPath: "id" });
// Create an index on the 'category' property
store.createIndex("by_category", "category", { unique: false });
// Create an index on the 'price' property
store.createIndex("by_price", "price", { unique: false });
console.log('Object store and indexes are ready.');
// The transaction for onupgradeneeded is still active,
// so we can add initial data here.
const initialProducts = [
{ id: 'p1', name: 'Modern JavaScript', category: 'books', price: 29.99 },
{ id: 'p2', name: 'Web Development', category: 'books', price: 39.99 },
{ id: 'p3', name: 'Mechanical Keyboard', category: 'electronics', price: 150.00 }
];
// To add data, we need to access the store again via the transaction
const productStore = event.target.transaction.objectStore('products');
initialProducts.forEach(product => {
productStore.add(product);
});
console.log('Initial products seeded.');
};

If an object store is no longer needed, you can delete it using db.deleteObjectStore(). Like creation, this must also be done inside an onupgradeneeded event, which means you must open the database with a new version number.

// Let's assume 'StoreDB' is at version 1 and has an 'obsoleteStore'.
// To delete it, we open the DB with a higher version number.
const request = indexedDB.open("StoreDB", 2);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// Check if the store exists before trying to delete it
if (db.objectStoreNames.contains('obsoleteStore')) {
db.deleteObjectStore("obsoleteStore");
console.log("'obsoleteStore' has been deleted.");
}
};