This content originally appeared on Go Make Things and was authored by Go Make Things
Yesterday, we looked at what indexedDB is, and how to create databases. Today, we’re going to learn how to create stores (similar to tabs in SQL or collections in MongoDB).
Let’s dig in!
Creating a database store
Now that we have a database, we need to create a store to add our wizards to.
The indexedDB.open()
method has an additional event: onupgradeneeded
. This event fires whenever a database is created for the first time or when the version number changes.
Inside the onupgradeneeded
callback function, we can access the oldVersion
number on the event
object. If it’s less than the current version, we can run any required code: in this case, creating a store.
To trigger it to run, let’s bump our version
number from 1
to 2
.
// Open a database
let openDB = indexedDB.open('spellbook', 2);
// If the version has increased or there's no existing DB
openDB.onupgradeneeded = function (event) {
// Get the database and previous version number
let db = openDB.result;
let oldVer = event.oldVersion;
// If there's no wizards store, create it
if (oldVer < 2) {
// Create the store...
}
};
To create a new store, we can use the IDBDatabase.createObjectStore()
method on our database.
The method requires just one argument: the name
of the store. Let’s call ours wizards
.
openDB.onupgradeneeded = function (event) {
// Get the database and previous version number
let db = openDB.result;
let oldVer = event.oldVersion;
// If there's no wizards store, create it
if (oldVer < 2) {
db.createObjectStore('wizards');
}
};
If we use the IDBDatabase.createObjectStore()
method with just one argument, we will need to specify a unique key
for each item with add to the database.
There’s an optional second argument, an object of options
, that you can provide to automate the process instead.
If the value of each item in your database is going to be an object, the keyPath
property lets you specify a property in that object to use as the key
for the item.
For example, imagine if each wizard in the database looked like this.
let merlin = {
name: 'Merlin',
spells: ['Summon Owl', 'Dancing Teacups']
};
If we use {keyPath: 'name'}
as our options, this entry would have a key
of Merlin
in the database, since that’s the name
property in our object.
openDB.onupgradeneeded = function (event) {
// Get the database and previous version number
let db = openDB.result;
let oldVer = event.oldVersion;
// If there's no wizards store, create it
if (oldVer < 2) {
db.createObjectStore('wizards', {keyPath: 'name'});
}
};
Alternatively, you can pass in {autoIncrement: true}
to have a random incremental key automatically generated by the database.
Transactions
The indexedDB API uses a concept called transactions to group a collection of operations or tasks together.
The operations don’t complete and write changes to the database until all of them are successful. This helps prevent data loss.
For example, imagine if I had a database for a bank. I wanted to take money out of John’s account, and move it into Sally’s. After the task to remove the money from John’s account finishes, but before it’s moved into Sally’s account, the power goes out. What happens to the money?
Transactions in the indexedDB API prevent the money from being lost in a situation like this. The removal of money from John’s account isn’t saved until it’s in Sally’s account, and the money added to Sally’s account isn’t saved until it’s removed from John’s account.
Every task in a transaction succeeds, or none of them do.
Creating a transaction
Any time you want to get, set, or delete data from a database, you need to create a transaction.
To create a transaction, use the IDBDatabase.transaction()
method. It accepts two arguments: the storenames
the transaction is for (as either a string or an array of strings), and the mode
.
If you’ll only be reading data, use readonly
for mode
. If you’ll be saving data, use readwrite
instead.
// Create a transaction as a read/write operation
let tx = db.transaction('wizards', 'readwrite');
Next, we need to get the store
we’ll be reading or writing our data to using the IDBTransaction.objectStore()
method. Pass in the store name
as an argument.
The store
, and any tasks you run on it, are now associated with the transaction (tx
).
// Create a transaction as a read/write operation
let tx = db.transaction('wizards', 'readwrite');
// Get the store for this transaction
let store = tx.objectStore('wizards');
If you’re only using a single store for this transaction, you can combine these into a single line. For transactions involving multiple stores, the transaction should be saved to its own variable.
// For single-store transactions, these can be combined
let store = db.transaction('wizards', 'readwrite').objectStore('wizards');
Transactions automatically commit
In the indexedDB API, transactions autocommit.
When all of the tasks associated with a transaction are done, the transaction automatically completes and commits those changes.
Because this happens automatically, you can’t run asynchronous code in the middle of a transaction (such as fetching data from an API). If you do, the transaction will commit and end before you get a response from your API.
// For single-store transactions, these can be combined
let store = db.transaction('wizards', 'readwrite').objectStore('wizards');
// Get data to add to the database from an API
fetch('https://jsonplaceholder.typicode.com/todos').then(function (response) {
return response.json();
}).then(function (data) {
// If you try to write data to the store here, it will fail
// The transaction associated with the store will have already committed and closed
});
If you need to get data asynchronously to write to your database, fetch it first, then create a transaction and use it.
// Get data to add to the database from an API
fetch('https://jsonplaceholder.typicode.com/todos').then(function (response) {
return response.json();
}).then(function (data) {
// This WILL work, since the data is already available when you create your transaction
// Create a transaction as a read/write operation
let tx = db.transaction('wizards', 'readwrite');
// Get the store for this transaction
let store = tx.objectStore('wizards');
});
Adding data to a database store
Now that we have a database and a store, we’re ready to add some data to it.
In a real app, you can add data to your database in all sorts of ways: from user inputs, API calls, and more. For this lesson, let’s add an array of wizards
when the database successfully opens.
Inside the onsuccess
callback function, we’ll pass db
, the database, into an addWizards()
function.
// If the database was successfully opened
openDB.onsuccess = function () {
// Get the database
let db = openDB.result;
// Add wizards to the database
addWizards(db);
};
Inside the addWizards()
function, we have an array of wizards
. Each one has a name
and an array of spells
that they know.
We want to add an entry in the wizards
store for each wizard in the array.
// Add wizards to the database
function addWizards (db) {
// Wizard data
let wizards = [
{
name: 'Merlin',
spells: ['Summon', 'Dancing Teacups']
},
{
name: 'Gandalf',
spells: ['Vanish', 'Flood', 'Light', 'Fire']
},
{
name: 'Radagast',
spells: ['Summon', 'Talk to Animals']
}
];
}
Next, we’ll create a transaction and get the wizards
store.
// Create a transaction and get the store
let store = db.transaction('wizards', 'readwrite').objectStore('wizards');
Now, we can loop through each wizard
in the wizards
array and add it to the database with the store.add()
method.
Pass in the item
to add as an argument. If your store
has a keyPath
or uses autoIncrement
, it will create a key
automatically. If not, you can pass one in as a second argument.
// Add each wizard to the database
for (let wizard of wizards) {
// Add the wizard
let request = store.add(wizard);
}
This is an asynchronous action.
Once again, we can use onsuccess
and onerror
events to run callback functions when our request
succeeds or fails.
// Add each wizard to the database
for (let wizard of wizards) {
// Add the wizard
let request = store.add(wizard);
// Log the success
request.onsuccess = function () {
console.log('Wizard added:', request.result);
};
// Log the error
request.onerror = function () {
console.warn(request.error);
};
}
The IDBObjectStore.add()
method adds an item to the database if it doesn’t already exist. If an item is already in the database, it will fail and throw an error instead.
If you tried to run the code above more than once, for example, this warning would log to the console.
// DOMException: Key already exists in the object store.
Updating data in a store
The IDBObjectStore.put()
method adds an item to the database if it doesn’t already exist, and updates an existing item if it does.
The openDB.onsuccess
callback function, let’s run a function called updateWizards()
, and pass the db
in as an argument.
// If the database was successfully opened
openDB.onsuccess = function () {
// Get the database
let db = openDB.result;
// Update wizards in the database
updateWizards(db);
};
Inside the updateWizards()
function, we’ll create an updated object for wizards
in the database.
Next, we’ll create a new transaction and get the wizards
store. Then, we’ll loop through each one and use the store.put()
method to update the entry in the database.
Because Merlin
already exists, it will be updated in the database. Jafar
is new, and will be added.
// Update wizards in the database
function updateWizards (db) {
// Wizard data
let wizards = [
{
name: 'Merlin',
spells: ['Summon', 'Dancing Teacups', 'Heal']
},
{
name: 'Jafar',
spells: ['Hypnosis']
}
];
// Create a transaction and get the store
let store = db.transaction('wizards', 'readwrite').objectStore('wizards');
// Update each wizard to the database
for (let wizard of wizards) {
// Update the wizard
let request = store.put(wizard);
}
}
Once again, we can use onsuccess
and onerror
events to run callback functions when our request
succeeds or fails.
// Update each wizard to the database
for (let wizard of wizards) {
// Update the wizard
let request = store.put(wizard);
// Log the success
request.onsuccess = function () {
console.log('Wizard updated:', request.result);
};
// Log the error
request.onerror = function () {
console.warn(request.error);
};
}
Deleting data from a store
The IDBObjectStore.delete()
method deletes an item from a store. Pass in the key
of the item to delete as an argument.
Inside the openDB.onsuccess
event, let’s run a deleteWizard()
function and pass in the db
as an argument.
// If the database was successfully opened
openDB.onsuccess = function () {
// Get the database
let db = openDB.result;
// Delete a wizard from the database
deleteWizard(db);
};
In the deleteWizard()
function, we’ll first create a new transaction, and get the wizards
store.
Next, we’ll use the store.delete()
method to delete Gandalf
from the database. As always, we can attach onsuccess
and onerror
events to the request
to run callback functions if the request
succeeds or fails.
// Delete a wizard from the database
function deleteWizard (db) {
// Create a transaction and get the store
let store = db.transaction('wizards', 'readwrite').objectStore('wizards');
// Update the wizard
let request = store.delete('Gandalf');
// Log the success
request.onsuccess = function () {
console.log('Wizard deleted:', request.result);
};
// Log the error
request.onerror = function () {
console.warn(request.error);
};
}
Digging deeper into indexedDB
If you want to dig into more advanced topics than we covered in this series, I have a guide and ebook on this topic that you might enjoy.
Get unlimited access to a JavaScript expert. If you're working on a critical project, I can help you reduce your risk and avoid costly delays caused by common mistakes. Learn more about consulting with me.
This content originally appeared on Go Make Things and was authored by Go Make Things
Go Make Things | Sciencx (2022-12-07T14:30:00+00:00) Creating and reading database stores in indexeddb. Retrieved from https://www.scien.cx/2022/12/07/creating-and-reading-database-stores-in-indexeddb/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.