IndexedDB explained.
In our previous article, we discussed Dexie, a wrapper for IndexedDB. In this article, we discuss IndexedDB. You must be familiar with this localStorage API, commonly used to store info in the browser. Similarly, IndexedDB is used for client side storage.
What is IndexedDB?
MDN documentation explaination:
IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files/blobs. This API uses indexes to enable high-performance searches of this data. While Web Storage is useful for storing smaller amounts of data, it is less useful for storing larger amounts of structured data.
IndexedDB provides a solution. This is the main landing page for MDN’s IndexedDB coverage — here we provide links to the full API reference and usage guides, browser support details, and some explanation of key concepts.
Example repository:
MDN provides an example Github repository and has script/todo.js.
Script is initialized using window.onload
window.onload = () => {
}
Open a request to database:
// Let us open our database
const DBOpenRequest = window.indexedDB.open('toDoList', 4);
Connection error:
// Register two event handlers to act on the database being opened successfully, or not
DBOpenRequest.onerror = (event) => {
note.appendChild(createListItem('Error loading database.'));
};
On successful database connection:
DBOpenRequest.onsuccess = (event) => {
note.appendChild(createListItem('Database initialised.'));
// Store the result of opening the database in the db variable. This is used a lot below
db = DBOpenRequest.result;
// Run the displayData() function to populate the task list with all the to-do list data already in the IndexedDB
displayData();
};
Add data
// Open a read/write DB transaction, ready for adding the data
const transaction = db.transaction(['toDoList'], 'readwrite');
// Call an object store that's already been added to the database
const objectStore = transaction.objectStore('toDoList');
// Make a request to add our newItem object to the object store
const objectStoreRequest = objectStore.add(newItem[0]);
objectStoreRequest.onsuccess = (event) => {
// process data on success.
}
// Report on the success of the transaction completing, when everything is done
transaction.oncomplete = () => {
note.appendChild(createListItem('Transaction completed: database modification finished.'));
// Update the display of data to show the newly added item, by running displayData() again.
displayData();
};
// Handler for any unexpected error
transaction.onerror = () => {
note.appendChild(createListItem(`Transaction not opened due to error: ${transaction.error}`));
};
Observation: localStorage vs IndexedDB
You might have by now realize that there is a lot of code required just to add a record, you have asynchronous callbacks such as onerror and onsuccess. This is pointed in this stack exchange answer.
To simplify handling this IndexedDB, Dexie can be used.
Add data with Dexie:
export function AddFriendForm({ defaultAge } = { defaultAge: 21 }) {
const [name, setName] = useState('');
const [age, setAge] = useState(defaultAge);
const [status, setStatus] = useState('');
async function addFriend() {
try {
// Add the new friend!
const id = await db.friends.add({
name,
age
});
setStatus(`Friend ${name} successfully added. Got id ${id}`);
setName('');
setAge(defaultAge);
} catch (error) {
setStatus(`Failed to add ${name}: ${error}`);
}
}
return (
<>
<p>{status}</p>
Name:
<input
type="text"
value={name}
onChange={(ev) => setName(ev.target.value)}
/>
Age:
<input
type="number"
value={age}
onChange={(ev) => setAge(Number(ev.target.value))}
/>
<button onClick={addFriend}>Add</button>
</>
);
}
This wrapper API reminds me of ORMs such as Prisma and Drizzle.
About us:
At Thinkthroo, we study large open source projects and provide architectural guides. We have developed resubale Components, built with tailwind, that you can use in your project. We offer Next.js, React and Node development services.
Book a meeting with us to discuss your project.