Practical IndexedDB Module for Structured Browser-Based Data Storage
Overview
IndexedDB is a browser-based storage mechanism for structured data, resembling a relational database but offering higher capacity and greater flexibility. Unlike localStorage, IndexedDB is suitable for storing large volumes of data, handles complex structures, and operates asynchronously. It is a common choice for persistent client-side storage.
Key Features
- Asynchronous: Most operasions are non-blocking to keep the UI thread responsive.
- Flexible Data Structures: Stores JavaScript objects such as arrays, objects, and other structured data.
- High Capacity: Limit are usually much larger than
localStorage(typically 50 MB or more per origin in most browsers). - Transactional: Data operations run within transactions, ensuring atomic reads and writes.
- Indexing: Support for indexes that accelerate data retrieval.
Typical Operations
- Opening a database: Invoke
indexedDB.open()to create or open a database. - Creating object stores: similar to tables, they hold the actual data.
- Transactions: all writes and reads are performed inside transactions.
- Data manipulation:
get,put,deleteand similar methods. - Index creation: define indexes on object properties to speed up queries.
Encapsulating IndexedDB in a Reusable Module
To make IndexedDB easier to use, the following module wraps common operations such as opening a database, saving data, retrieving data, and deleting records.
Module: storageEngine.js
/**
* Open a (new or existing) database and ensure the given store is ready.
*/
export const initDatabase = (dbLabel, storeLabel) => {
return new Promise((resolve, reject) => {
const connectionReq = indexedDB.open(dbLabel, 1);
connectionReq.onupgradeneeded = (event) => {
const dbInstance = event.target.result;
if (!dbInstance.objectStoreNames.contains(storeLabel)) {
const objectStore = dbInstance.createObjectStore(storeLabel, { keyPath: 'recordId' });
objectStore.createIndex('contentIndex', 'payload');
}
};
connectionReq.onsuccess = (event) => resolve(event.target.result);
connectionReq.onerror = (event) =>
reject('Database open error: ' + event.target.errorCode);
});
};
/**
* Persist a document into the target store.
*/
export const persistDocument = (dbHandle, storeLabel, doc) => {
return new Promise((resolve, reject) => {
const txn = dbHandle.transaction(storeLabel, 'readwrite');
const store = txn.objectStore(storeLabel);
const putReq = store.put(doc);
putReq.onsuccess = () => resolve('Document stored successfully');
putReq.onerror = (event) =>
reject('Store failed: ' + event.target.errorCode);
});
};
/**
* Fetch a single document by its primary key.
*/
export const fetchDocument = (dbHandle, storeLabel, recordId) => {
return new Promise((resolve, reject) => {
const txn = dbHandle.transaction(storeLabel, 'readonly');
const store = txn.objectStore(storeLabel);
const getReq = store.get(recordId);
getReq.onsuccess = (event) => resolve(event.target.result);
getReq.onerror = (event) =>
reject('Fetch failed: ' + event.target.errorCode);
});
};
/**
* Remove a document identified by its primary key.
*/
export const deleteDocument = (dbHandle, storeLabel, recordId) => {
return new Promise((resolve, reject) => {
const txn = dbHandle.transaction(storeLabel, 'readwrite');
const store = txn.objectStore(storeLabel);
const delReq = store.delete(recordId);
delReq.onsuccess = () => resolve('Document removed');
delReq.onerror = (event) =>
reject('Deletion failed: ' + event.target.errorCode);
});
};
Usage Examples with React Hooks
Initializing the Database
const [dbConnection, setDbConnection] = useState(null);
useEffect(() => {
const bootstrap = async () => {
const connection = await initDatabase('AnalyticsDB', 'payloadStore');
setDbConnection(connection);
};
bootstrap();
}, []);
Retrieving Data
const loadCachedPayload = async () => {
if (!dbConnection) return fallbackPayload;
try {
const doc = await fetchDocument(dbConnection, 'payloadStore', currentKey);
return doc ? doc.payload : fallbackPayload;
} catch (err) {
console.error('Unable to read from IndexedDB', err);
return fallbackPayload;
}
};
Saving Data
const commitPayload = async (newPayload) => {
if (!dbConnection) return;
try {
await persistDocument(dbConnection, 'payloadStore', {
recordId: currentKey,
payload: newPayload,
});
console.log('Payload committed to IndexedDB');
} catch (err) {
console.error('Commit error', err);
}
};
Deleting Data
const clearCachedPayload = async () => {
if (!dbConnection) return;
try {
await deleteDocument(dbConnection, 'payloadStore', currentKey);
console.log('Cached payload removed from IndexedDB');
} catch (err) {
console.error('Removal error', err);
}
};
Summary
IndexedDBprovides an asynchronous, transactional, and high-capacity storage service inside the browser.- The
storageEngine.jsmodule wraps common operations—database enitialization, data insertion, retrieval, and deletion. - By integrating these functions with React, you can manage structured client‑side data without running into the size limits of
localStorage, while keeping complex data easily accessible.