Using the IndexedDB API with Javascript in 2024
July 26, 2023 | By David Selden-Treiman | Filed in: Javascript.Welcome, fellow web enthusiast! It’s great to have you here, ready to delve into the world of IndexedDB, one of the key technologies of the web universe. Whether you’re an experienced web developer looking to extend your knowledge or a newcomer who’s just embarking on their web development journey, this guide is designed for you. Let’s take this journey together and uncover the secrets of IndexedDB!
What is IndexedDB?
At its core, IndexedDB is a low-level API for client-side storage of significant amounts of structured data, including files and blobs. This allows you to create, read, update, and delete records in a database, all from your web application. Think of it as a way to have a database right in your user’s browser.
var request = window.indexedDB.open("MyTestDatabase", 3);
The line of code above is a simple example of how you can create or open a database using IndexedDB. Don’t worry if this looks a bit cryptic now. As we journey further into this article, all this will become as clear as a sunny day.
IndexedDB is different from other web storage solutions. It provides a more robust, powerful solution compared to, say, localStorage or sessionStorage, which are simpler and have limitations. Moreover, IndexedDB is asynchronous, meaning it doesn’t block the browser when it’s interacting with the database. This is a big deal as it provides a smoother user experience.
Over the course of this article, we’ll discuss IndexedDB in detail: what it is, why it is used, and how you can use it. We’ll also present easy-to-follow code examples and tips, so you’ll be building your own IndexedDB-based web applications in no time! So, buckle up and let’s get started on this journey of discovery!
Understanding IndexedDB
Building upon our brief introduction, let’s delve deeper into the world of IndexedDB.
What is IndexedDB, Really?
IndexedDB is a JavaScript-based object-oriented database system that allows you to store data inside a user’s browser. In simpler terms, it lets you create a personal mini database within each user’s browser, where you can stash data for later use. This can be anything from simple key-value pairs to more complex data structures like objects and arrays.
For instance, imagine you’re building a weather app. You can store the weather data of a user’s location in IndexedDB, allowing you to quickly display the weather without having to fetch the data every time the user opens the app.
Here’s a simple example of how you can store an object in an IndexedDB database:
var db;
var request = indexedDB.open("weatherApp");
request.onupgradeneeded = function(event) {
var db = event.target.result;
var objectStore = db.createObjectStore("locations", {keyPath: "id"});
};
request.onsuccess = function(event) {
db = event.target.result;
var transaction = db.transaction(["locations"], "readwrite");
var objectStore = transaction.objectStore("locations");
var request = objectStore.add({id: 1, location: "London", temperature: "15°C"});
};
How IndexedDB Differs from Other Web Storage Solutions
To fully appreciate the power of IndexedDB, it’s helpful to understand how it differs from other client-side web storage solutions.
Compared to localStorage and sessionStorage
localStorage
and sessionStorage
are both synchronous web storage options that let you store data in a user’s browser. However, they’re limited in storage size, typically to 5-10MB. Additionally, they only allow you to store strings. If you want to store objects, you’d need to serialize them to strings and then parse them back into objects when retrieving them.
IndexedDB, on the other hand, offers a much larger storage capacity (in the order of hundreds of MB or even more, depending on the browser). It also natively supports storing a variety of data types, including simple data types (like numbers, strings, and Booleans), JavaScript objects, and binary data types (like Blobs, Files, and ArrayBuffers).
Compared to WebSQL and Cookies
WebSQL was once a popular choice for client-side storage because it allowed structured data storage with SQL. However, it’s no longer maintained, and the use of WebSQL is now discouraged.
Cookies, another method of storage, are very limited in size (around 4KB), and every cookie you store is sent with each HTTP request, adding unnecessary load to your requests.
In contrast, IndexedDB doesn’t use SQL, instead opting for a more JavaScript-friendly API. It’s also more efficient than cookies, as it doesn’t add to your HTTP request size.
From these comparisons, it’s easy to see why IndexedDB is often the preferred choice for complex, modern web applications. In the next section, we’ll delve into why IndexedDB is used and some common use cases. Let’s dive in!
Why IndexedDB is Used
Now that you’ve learned what IndexedDB is and how it stands apart from other web storage solutions, let’s talk about why it’s so widely used.
Advantages of IndexedDB
IndexedDB brings a lot to the table when it comes to building dynamic, data-driven web applications. Let’s delve into some of its major advantages.
Large Storage Capacity
As previously mentioned, IndexedDB allows for a larger amount of data to be stored in the user’s browser. This capacity can reach up to a few gigabytes depending on the browser, which is a lot more than what other client-side storage options offer.
Indexing and Querying Capabilities
IndexedDB isn’t just a place to store data; it’s also a full-fledged database system. It provides powerful features like indexing, which allows for faster data retrieval, and querying, which enables you to search through your data efficiently.
For instance, if you’re building a web-based email client, you could create an index on the sender
field of an email
object store. This index would allow you to quickly and efficiently find all emails from a specific sender.
var transaction = db.transaction(["emails"], "readwrite");
var objectStore = transaction.objectStore("emails");
var index = objectStore.createIndex("by_sender", "sender", {unique: false});
Asynchronous Operations
Unlike localStorage
and sessionStorage
, IndexedDB operations are non-blocking. This means your application can continue doing other things while IndexedDB operations are being processed. This results in a smoother, more responsive user experience.
Use Cases of IndexedDB
There are a variety of situations where IndexedDB can come in handy. Below are some common use cases.
Offline-capable Web Applications
If you’re creating a web application that needs to work offline, IndexedDB is a perfect choice. It allows you to store data locally, providing your users with a seamless offline experience. When the app goes back online, it can then sync the locally stored data with a server.
Storing and Retrieving Large Amounts of Data on the Client-side
If your web app needs to handle a significant amount of data on the client-side, IndexedDB can be a real lifesaver. Think about an image editing app that allows users to apply filters to photos. IndexedDB could store the images locally while the user is editing, reducing the need for constant server communication.
Applications that Require Advanced Data Manipulation on the Client-side
For applications that need to perform complex data manipulations on the client-side, IndexedDB is a solid choice. For example, a data visualization app could use IndexedDB to store and manipulate the data that powers the visualization.
By now, you should have a pretty solid understanding of what IndexedDB is and why it’s used. In the next section, we’ll start digging into the real meat of IndexedDB: how to use it. Get ready, because this is where the fun really begins!
Fundamentals of IndexedDB
You’ve got the basics down—now, let’s start digging into the nitty-gritty. Here’s where you’ll learn about the core concepts of IndexedDB and get acquainted with the IndexedDB API. Let’s go!
Core Concepts in IndexedDB
Before you start using IndexedDB, it’s important to grasp its core concepts. These include object stores, indexes, and transactions.
Object Stores
Object stores in IndexedDB are somewhat analogous to tables in relational databases. Each object store holds records, and each record is a key-value pair. Records within an object store are sorted according to their keys.
Let’s look at an example of how to create an object store:
var db;
var request = indexedDB.open("library", 1);
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore("books", {keyPath: "id"});
};
In this example, we’re creating an object store named “books”. Each book in the object store will have a unique “id”.
Indexes
An index is a kind of object store for organizing the data in another object store, allowing for faster searches. It’s like a sorted list of all the values of a particular property in the object store.
Let’s take a look at how to create an index:
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore("books", {keyPath: "id"});
var index = objectStore.createIndex("by_author", "author", {unique: false});
};
In this example, we’re creating an index named “by_author” on the “author” field of our “books” object store. This would allow us to quickly find all books by a particular author.
Transactions
A transaction is a wrapper around an operation, or group of operations, ensuring that all parts of the operation(s) succeed or fail together. It ensures the integrity of the database by locking the database for writing by only one transaction at a time.
Here’s an example of how to create a transaction:
request.onsuccess = function(event) {
db = event.target.result;
var transaction = db.transaction(["books"], "readwrite");
var objectStore = transaction.objectStore("books");
var request = objectStore.add({id: 1, title: "Moby Dick", author: "Herman Melville"});
};
In this example, we’re creating a transaction that includes an operation to add a book to our “books” object store.
Detailed Explanation of the IndexedDB API
Now, let’s understand the main interfaces of the IndexedDB API that you’ll interact with while using IndexedDB.
IDBDatabase interface
The IDBDatabase interface provides a connection to a database. You can create object stores and transactions and close a connection to a database through this interface.
IDBObjectStore and IDBIndex interfaces
The IDBObjectStore interface represents an object store. It provides methods for adding, deleting, and retrieving records from the object store.
Similarly, the IDBIndex interface represents an index and provides methods to retrieve and organize data in an object store.
IDBTransaction interface
The IDBTransaction interface provides a static, asynchronous transaction on an IDBDatabase. It handles the lifecycle of a transaction and provides a means for error handling.
IDBRequest interface
The IDBRequest interface provides access to results of asynchronous requests to databases and database objects. Every operation you perform in IndexedDB involves a request.
Congratulations! You’ve now grasped the core concepts of IndexedDB and its API. In the next section, we’re going to go hands-on and learn how to use IndexedDB with some practical code examples. Keep this enthusiasm going—you’re doing great!
Getting Hands-on with IndexedDB
By now, you’re familiar with the core concepts of IndexedDB and have seen bits of the IndexedDB API in action. But as the saying goes, “practice makes perfect!” So let’s get hands-on and start using IndexedDB.
Opening a Database
The first step in using IndexedDB is to open a database. Here’s how you do it:
let db;
let request = indexedDB.open("MyDatabase", 1);
request.onupgradeneeded = function(event) {
db = event.target.result;
let objectStore = db.createObjectStore("MyObjectStore", {keyPath: "id"});
};
request.onsuccess = function(event) {
db = event.target.result;
};
request.onerror = function(event) {
console.log("Error opening database: ", event.target.errorCode);
};
In this example, we’re opening a database named “MyDatabase”. If the database doesn’t exist, it will be created. The onupgradeneeded
event is fired if the database is being created for the first time or if a new version of the database is being opened. Inside this event, we create an object store named “MyObjectStore”.
Adding Data to an Object Store
Once you’ve opened a database and created an object store, you can start adding data. Let’s add some data to our object store:
let transaction = db.transaction(["MyObjectStore"], "readwrite");
let objectStore = transaction.objectStore("MyObjectStore");
let request = objectStore.add({id: 1, name: "John Doe", email: "john@doe.com"});
request.onsuccess = function(event) {
console.log("Data added successfully");
};
request.onerror = function(event) {
console.log("Error adding data: ", event.target.errorCode);
};
In this example, we’re starting a read-write transaction on our object store, which allows us to add, modify, or delete data. Then we’re adding an object to the object store. The onsuccess
event is fired when the data is added successfully, and the onerror
event is fired if there’s an error.
Retrieving Data from an Object Store
Let’s retrieve the data we just added:
let transaction = db.transaction(["MyObjectStore"]);
let objectStore = transaction.objectStore("MyObjectStore");
let request = objectStore.get(1);
request.onsuccess = function(event) {
console.log("Data retrieved: ", request.result);
};
request.onerror = function(event) {
console.log("Error retrieving data: ", event.target.errorCode);
};
In this example, we’re starting a read-only transaction (which is the default) on our object store and then retrieving the object with the key of 1.
Updating Data in an Object Store
Updating data in an object store is as simple as adding data—you just use the put
method instead of add
. The put
method updates if the record exists or adds it if it doesn’t:
let transaction = db.transaction(["MyObjectStore"], "readwrite");
let objectStore = transaction.objectStore("MyObjectStore");
let request = objectStore.put({id: 1, name: "John Doe", email: "johnny@doe.com"});
request.onsuccess = function(event) {
console.log("Data updated successfully");
};
request.onerror = function(event) {
console.log("Error updating data: ", event.target.errorCode);
};
Deleting Data from an Object Store
Deleting data is also straightforward—you use the delete
method:
let transaction = db.transaction(["MyObjectStore"], "readwrite");
let objectStore = transaction.objectStore("MyObjectStore");
let request = objectStore.delete(1);
request.onsuccess = function(event) {
console.log("Data deleted successfully");
};
request.onerror = function(event) {
console.log("Error deleting data: ", event.target.errorCode);
};
Congratulations! You’ve successfully performed CRUD operations with IndexedDB. Now that you’ve got the basics down, you’re well on your way to harnessing the full power of IndexedDB in your web applications. In the next section, we’ll discuss how to handle errors and close a database. Keep up the good work—you’re almost at the finish line!
Handling Errors and Closing a Database in IndexedDB
You’re doing great so far! You’ve learnt how to perform CRUD operations using IndexedDB. Now, let’s look at how to handle errors and properly close a database. These are essential parts of working with any database system, and IndexedDB is no exception.
Handling Errors in IndexedDB
When you’re working with IndexedDB, things don’t always go as planned. That’s why it’s crucial to know how to handle errors. Each request has an onerror
event that is triggered when something goes wrong with the request. Here’s a simple example:
let transaction = db.transaction(["MyObjectStore"], "readwrite");
let objectStore = transaction.objectStore("MyObjectStore");
let request = objectStore.add({id: 1, name: "John Doe", email: "john@doe.com"});
request.onerror = function(event) {
console.log("Error: ", event.target.errorCode);
};
In this example, if something goes wrong when adding data to the object store, the onerror
event is fired and the error code is logged to the console. Note that transactions also have onerror
events that are triggered when any request within the transaction fails.
Closing a Database
When you’re done using a database, it’s good practice to close it. This helps prevent data loss or corruption and other problems that can occur when a database is unexpectedly deleted or upgraded. Here’s how to close a database:
db.close();
It’s as simple as that! When you call the close
method on a database, all pending transactions are finished, but new transactions can no longer be created.
Handling Version Changes
When you open a database with a higher version number, the onupgradeneeded
event is triggered. This event is where you typically create and update object stores and indices. But what happens when another part of your application (or another tab in the browser running your application) tries to open the database with a higher version number while you’re in the middle of something?
IndexedDB handles this by firing a versionchange
event on all open database connections. You can listen for this event and close the database so that the other request can upgrade the database. Here’s how:
db.onversionchange = function(event) {
db.close();
console.log('The database is being upgraded or deleted');
};
In this example, when a versionchange
event is fired, the database is closed and a message is logged to the console.
Great job! You’ve now covered all the essential parts of IndexedDB. Keep practicing, exploring, and building. As with any technology, the more you use IndexedDB, the more comfortable and proficient you’ll become. You’re now well-equipped to use IndexedDB to create more powerful and interactive web applications.
Best Practices when using IndexedDB
Bravo! You’ve mastered the essentials of IndexedDB. But before you dive into building applications with IndexedDB, let’s take a look at some best practices that can help you write cleaner, more efficient code, and prevent some common pitfalls.
Avoid Premature Optimization
As you start writing code with IndexedDB, you might be tempted to over-optimize. While performance is important, remember that premature optimization can make your code more complex and harder to maintain. Start by writing clear, straightforward code. Then, once you have a working application, use profiling tools to identify bottlenecks and optimize as needed.
Handle Promises Correctly
Many IndexedDB operations return Promises. It’s crucial to handle these promises correctly to avoid uncaught exceptions and to ensure that errors are dealt with appropriately. Always include a .catch()
block when working with Promises. This way, if something goes wrong, you’ll be able to handle it.
db.transaction(["MyObjectStore"], "readwrite")
.objectStore("MyObjectStore")
.add({id: 1, name: "John Doe", email: "john@doe.com"})
.catch(function(error) {
console.log("Error: ", error);
});
In this example, if adding data to the object store fails, the error will be caught and logged to the console.
Use Transactions Wisely
Transactions are a powerful feature of IndexedDB, but they can also be a source of complexity. When you’re performing multiple operations that need to succeed or fail together, use transactions. But be mindful not to wrap too many operations in a single transaction, as this can lead to performance issues and make error handling more complicated.
Close Databases When Done
As discussed in the previous section, it’s good practice to close a database when you’re done with it. This can help prevent issues related to concurrent access and versioning.
db.close();
Use the Right Data Types
IndexedDB is a NoSQL database and doesn’t enforce any particular schema for the data you store. However, it’s important to use the right data types for your data to ensure efficient storage and retrieval. For instance, use Dates rather than strings for date values, and use Arrays and Objects to store structured data.
let transaction = db.transaction(["MyObjectStore"], "readwrite");
let objectStore = transaction.objectStore("MyObjectStore");
let request = objectStore.add({
id: 1,
name: "John Doe",
email: "john@doe.com",
createdAt: new Date(),
preferences: {
color: "blue",
language: "English"
}
});
In this example, we’re using a Date for the createdAt
field and an Object for the preferences
field.
That’s it! With these best practices, you’ll be able to write better, more efficient, and easier-to-maintain IndexedDB code. Now you’re ready to start creating robust web applications with IndexedDB.
Going Beyond: IndexedDB Libraries and Next Steps
Hats off to you! You’ve come a long way in your journey to understand IndexedDB. Let’s now venture beyond the basics. We’ll explore some libraries that can make working with IndexedDB even easier and discuss your next steps in mastering IndexedDB.
IndexedDB Libraries
While IndexedDB’s API is quite powerful, it can sometimes be verbose and complicated. Luckily, there are several libraries available that provide a simpler, more intuitive API on top of IndexedDB:
1. Dexie.js
Dexie.js is a minimalistic wrapper for IndexedDB. It simplifies IndexedDB’s API without hiding any of its power. With Dexie.js, you can write less code and achieve more. Here’s an example of how you might use Dexie.js:
let db = new Dexie("MyDatabase");
db.version(1).stores({
MyObjectStore: 'id'
});
db.MyObjectStore.put({id: 1, name: "John Doe", email: "john@doe.com"})
.then(function() {
return db.MyObjectStore.get(1);
})
.then(function(user) {
console.log("User: ", user);
})
.catch(function(error) {
console.error("Oops, an error occurred: ", error);
});
2. localForage
localForage is another library that simplifies working with IndexedDB. What makes localForage unique is that it also provides a fallback to WebSQL or localStorage if IndexedDB is not available, making it a good choice for applications that need to work across a variety of browsers:
localforage.setItem('key', 'value').then(function() {
return localforage.getItem('key');
}).then(function(value) {
console.log(value);
}).catch(function(err) {
console.error(err);
});
Next Steps
Now that you’ve gotten your feet wet with IndexedDB, what’s next? Here are a few suggestions:
- Experiment: The best way to learn is by doing. Try to build a simple web app that uses IndexedDB to store data. Experiment with different features of IndexedDB that we didn’t cover in this article, like cursors and indexes.
- Read the Spec: The IndexedDB spec is a comprehensive resource that covers every detail of IndexedDB. While it can be dense, it’s worth reading if you want to understand every aspect of IndexedDB.
- Explore Further Resources: There are plenty of resources out there that can help you deepen your understanding of IndexedDB. MDN’s IndexedDB guide is a great place to start.
You’ve already made great strides in understanding IndexedDB. Keep practicing, learning, and experimenting, and you’ll soon be an IndexedDB expert.
Wrapping Up: The Power of IndexedDB
Congratulations! You’ve journeyed through the intricacies of IndexedDB, one of the most potent client-side storage APIs available in web browsers today. By this point, you’re well-armed with the knowledge you need to implement robust, efficient data storage in your web applications.
The Importance of IndexedDB
IndexedDB truly shines when it comes to creating rich, offline-first experiences. With the increasing focus on performance, reliability, and seamless user experiences, knowledge of IndexedDB is a significant asset for any web developer.
Consider this: With IndexedDB, your application can operate offline, persist user data across sessions, handle large amounts of structured data, and perform complex search queries on the client-side. It’s a boon for users who face intermittent network connectivity or who need to work with data-heavy applications.
Keep Practicing!
Remember, like with any technology, your mastery over IndexedDB will only deepen with practice. Use it in your personal projects, create a web app requiring offline data storage, or simply continue to explore different facets of this API. As you do, you’ll find your understanding of it – and its usefulness in your toolkit – will only grow.
The Future of IndexedDB
As we look ahead, the future of IndexedDB seems promising. The standard continues to evolve, and web browsers constantly improve their implementation, ensuring that IndexedDB remains a robust and powerful choice for web developers. Stay informed about updates to the specification and continually explore ways to use new features in your applications.
To conclude, IndexedDB is a powerful tool in the right hands, and now, you are well on your way to becoming a pro at it! Take these learnings, apply them, and continue to explore and innovate. You’ve got a fantastic journey ahead.
Happy coding!
David Selden-Treiman is Director of Operations and a project manager at Potent Pages. He specializes in custom web crawler development, website optimization, server management, web application development, and custom programming. Working at Potent Pages since 2012 and programming since 2003, David has extensive expertise solving problems using programming for dozens of clients. He also has extensive experience managing and optimizing servers, managing dozens of servers for both Potent Pages and other clients.
Comments are closed here.