 If we're going to build offline applications, we need to store more than static assets. We need to store data. One of the best places to do this is in a database. Fortunately, we have one called IndexedDB. For example, we might have a database of musicians. The table on the screen lists the form members of the Beatles. Let's look at how we could put this into a database. Let's start with what IndexedDB isn't. It's not a relational database, and it doesn't use SQL. You could build these features on top of it, but that's not what you get right out of the box. IndexedDB is an object store. It holds JavaScript objects. You can store objects, strings, arrays, numbers, anything that can be cloned, even files. You can do basic searching and sorting as long as you're not writing a complex query. It even supports transactions. When you open a database, you can add data to it directly, but you'll more commonly create an object store and add the data there. Each database can have multiple object stores. They're a little like a table. For example, there could be a user's object store containing user profile data. While the object store will assign a unique key to each user object, you can also define indexes such as index by email address. We can then use the index to retrieve the data in the object store by the email, as well as the unique key. You saw the musician's table earlier. Now let's look at the musician's object store. We've defined the key to be the artist's name, which we extract from the name property in the object. We could set indexes on the other properties if we want, though here we're not. It's time to look at some actual code. The raw indexedDB API is a bit tricky to work with. It's an older asynchronous API that uses callbacks and fires events to signal completion and errors. To make things easier, we'll use the indexedDB promise library, a small wrapper around the indexedDB API that uses promises. This was written by Jake Archibald here at Google and is open source. If you want to see the original API, MDN has an excellent write up. We're going to follow the usual pattern of opening a database and creating object stores. We'll then populate those stores with data. IDB.open creates or opens a database. It takes a name, version number, and an optional callback function to manage the object stores. It also returns a promise. Why use a callback function here? The object stores can only be created and destroyed during the open call. Putting them in a trailing promise would be too late. The upgrade callback is only called when the database is first created or if the version number is greater than the existing version number, namely an upgrade. The next step is to create the object stores. Before I show you some code, let's talk about what goes into each object store. You want all of the objects in a store to have the same format so you can index them. Some objects may have extra properties, but you want the core properties to be all the same. Now that's out of the way. Let's take a look in the upgrade callback. Inside the upgrade callback we'll receive an upgrade database object. We can then create the object store by calling create object store. If we try to create an existing object store, we'll get an error. So remember to check the object store names first. Like any other database, the object store can have a primary key. This is a value that uniquely identifies each object in the store. If you want a primary key, you need to define it when you create the object store. You do this by passing an options object after the name. In the first example, we're using an email address as the key. The email address has to be unique for each object. The second and third examples use a key generator to assign a serial number to each object. The second example stores the number separate from the object, and the third example stores it in the object's ID property. Sometimes you want to search on something other than the primary key. You can do this by adding an index for another property. Indexing makes the lookups faster, but it also makes the database larger, so use these with care. To create the indexes, call create index on the object store. In this example, we're adding an email index to the store. We can add the unique, call-and-true option to make sure each address is unique. You can also add a multi-entry option for arrays. This chooses between indexing every value in the array or treating the whole array as a key. Now that we have a database with object stores, let's see how to get data in and out. Index DB supports the usual crud operations, create, retrieve, update, and delete. It also uses transactions to group these operations together. These operations should look pretty familiar. Add, get, put, delete, and get all do what you expect. Cursor, however, is something different. It extracts values by index or key. You have to wrap the operations in a transaction. This groups the operations together so they happen as a unit, such as reading a value, modifying it, and writing it back. Transactions help ensure the database is always in a consistent state. They're also important if you have multiple copies of your app running, as they prevent simultaneous writes to the same data. When you work with a transaction, you first open a transaction on the database. Transactions may be read-only or read-write. You then open any object stores and indexes you will need. Finally, you can perform the operations. It seems like a lot of steps, but it's needed to preserve the integrity of the database. Let's look at adding data. Remember that we're using Jake's Promise wrapper so the database itself is delivered as a promise. We let it resolve, create a read-write transaction, and then open the object store. Now we can add our value. Notice that we're returning transaction.complete at the end. This is a promise that settles when the transaction is done or has an error. It's important to wait for this when our transaction changes the database. Getting data is a bit simpler. Open a transaction in the object store, then call get. You don't have to wait for the transaction to complete as you're only reading. Getting the data is a good enough result, by the way. You may wonder when the transaction closes. IndexedDB manages transaction lifetimes automatically. You can safely assume they're closed by the next time the browser enters the idle loop. Updating data looks just like adding data, except that we're using put instead of add. This could be made into a read-modify-write operation by calling get, updating the object, and then writing it all in the same transaction. Call delete to remove an item. By now, you see the pattern. Since it changes the database, you need to wait for the transaction to complete. We haven't talked about how to search yet. IndexedDB lets you get a block of data based on a key or index. Any fancier searching is up to you. The easiest way is to get all the data in an array by calling get all. Since this is a read-only transaction, you don't need to wait for it to complete. Another way to retrieve all the data is to use a cursor. A cursor will select each object in an object store or index sequentially, allowing you to do something with each element. Once we get a cursor, we need to use it to read each element. This is a bit tricky. Essentially, you want a loop to get the value from the cursor, exit if the cursor is null, and call continue to advance the cursor. This is one of those rare spots where using promises makes this more complicated. This time, we've given our function a name so we can call it from inside itself recursively. To recap what this code is doing, we break out if the cursor is null. Otherwise, we get the object from the cursor's value property and print it. We then call continue to advance the cursor and recursive into our function. Take a moment and breathe. We're almost done. We don't have to get all the data. We can select a subset. Both open cursor and get all will take an IDB key range value to select a subset of values. You can specify any one of these three, lower bound, upper bound, or a plain bound with a lower and upper value. There's also an only method not shown here. And here we are. If we wanted all products from soda and later, sorted alphabetically, this is how we would do it. We create a range with a lower bound and pass it to get all. We're about to get to the lab, but first you may want to check out some of these polyfills and tools. These make IndexedDB much easier to use. Now it's your turn. Go to the IndexedDB lab. In there, you will get to practice and experiment with everything we've talked about here. Have fun and good luck.