 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. Hi, I'm Sarah Clark, and I'm here to introduce you to IndexedDB. 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 indices such as index by email address. You can then use the index to retrieve the data in the object store by the email as well as the unique key. Let's look at an object store for a set of musicians. We've defined the key to be the artist name, which we extract from the name property in an object. We could set indices on the other properties if we want, although here we're not. It's time to look at some actual code. The raw index DB 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 index DB promised library, a small wrapper around the index DB 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, 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 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 all be 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, each 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 lookups faster, but it also makes the database larger, so I 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, colon 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. Now cursor 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 the transaction, you first open a transaction on the database. Transactions may be read only or read write. You then open any object stores and indices 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 inside a promise. We let it resolve, create a read write transaction, 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 a transaction changes the database. Getting data is a bit simpler. Open a transaction and 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 should 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 fancy you're searching is up to you. The easiest way to get all the data in an array is 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 of 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 things more complicated. This time, we've given our function a name so we can call it from inside of 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 recurs 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 plain bound with a lower and upper value. There's also an only method that's 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 would 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 index DB easier to use. Now it's your turn. Go to the index DB lab. In there, you will get to practice and experiment with everything we've talked about here. Have fun and thanks for watching.