JavaScript: Building a To-Do App (Part 2)
Tags: javascript, tutorial, webdev, database
Published: 2020-07-19 21:14:46

### Introduction
Welcome to part two of the Introduction to the IndexedDB API series, last time we learned about the basics of the API and how we could perform some of the most common operations. In this part, we will begin the construction of our To-Do web application by applying that knowledge. Specifically, we will:

1. Get familiar with the boilerplate code of the application
2. Create a class that encapsulates the IndexedDB instance
3. Set the logic to create and save a new task

### Getting Started
To start, make sure you have a recent version of **NPM**, then go to the following **GitHub** repo [here](https://github.com/DevTony101/js-todo-app-indexed_db) and, in the `README`, find the `Want to Code Along?` section and download the code tagged as `starting code` for part one.  The project uses webpack and webpack-dev-server to create a local development server as well as **Bulma** (a modern CSS framework) for the front-end. Once you got it, execute the following commands:

```
npm install
npm run start
```
The first one will download all the dependencies, the second one will run the _start_ script defined in the `package.json` file and start the local server. If everything goes well, the server will start listening in the `port 8080` at the following URL:

- http://localhost:8080/dist/

#### Project Structure
Letting the `node_modules/` folder aside, the project has the following file structure:

```
│   .babelrc
│   .gitignore
│   package-lock.json
│   package.json
│   webpack.config.js
│
├───dist
│   │   index.html
│   │   main.js
│   │
│   └───assets
│       └───css
│               index.css
│
└───src
    │   index.js
    │
    └───database
            database.js
```
- The `dist/` folder contains the public files. We won't necessary edit these files but I encourage you to personalize the `index.html` file and create your layout

- The `src/` folder contains the **javascript** code, including the `index.js` file (the entry point of our application) and the `database.js` file that will export the `Database` class

- The rest of the files are configuration files and should not be changed

_(Note: It is not necessary to run the start script every time we make a change as the webpack-dev-server dependency will automatically compile the code and reload the page.)_

The `index.js` file already has a `saveTask` function that will be called when a `submit` event on the form is fired.

### Creating the Database
As said earlier, the `database.js` file contains a class. This class will encapsulate the IndexedDB instance and manage the methods for the CRUD operations. Let's start by creating the instance, remember that we need two things, the **name** and the **version** of the database, we can easily pass these information as variables to the constructor, like this:

```javascript
export default class Database {
  constructor(name, version) {
    this.name = name;
    this.version = version;
    this.indexedDB = {};
    this.database = window.indexedDB.open(name, version);
    this.database.onsuccess = () => {
      console.log(`Database ${name}: created successfully`);
      this.indexedDB = this.database.result;
    }
  }
}
```
Now, back in the `index.js` file, let's create a new instance of our `Database` class by passing in the name and version we want, like this:

![Screenshot IndexJS](https://dev-to-uploads.s3.amazonaws.com/i/1v6a30in2ra8ddrihdjo.png)

Now if we run the `start` script once again (remember, it's not necessary if it was already running) we will see the message `Database DBTasks: created successfully` but, if we _really_ want to make sure our database was created, we can go to the **Application** tab on the Chrome DevTools and check under `Storage > IndexedDB`:

![Screenshot Application Tab](https://dev-to-uploads.s3.amazonaws.com/i/1s0th8k7d34o2bo5k0ny.png)

As you can see, the database indeed exists but is empty.

### Creating the `persist` Function
Before we can attempt to save something remember, we first need to create the schema. We _could_ create the fields directly in the constructor, but we want the `Database` class to be capable of building different databases with different fields. For that purpose, let's pass another variable to the constructor called `fields` that can be either a string or an array and create the model from it, like this:

```javascript
export default class Database {
  constructor(name, version, fields) {
    // ...
    this.database.onupgradeneeded = event => {
      const instance = event.target.result;
      const objectStore = instance.createObjectStore(name, {
        keyPath: "key",
        autoIncrement: true,
      });

      if (typeof fields === "string") fields = fields.split(",").map(s => s.trim());
      for (let field of fields) objectStore.createIndex(field, field);
    }
  }
}
```
Now we can easily pass the various fields we could need for our database. The following code snippets are equivalent:

```javascript
const database = new Database("DBTasks", 1, "title, description");
```
```javascript
const database = new Database("DBTasks", 1, ["title", "description"]);
```
Now when the database is created, it will have an object store attached to it:

![Screenshot Application Tab](https://dev-to-uploads.s3.amazonaws.com/i/l7n7bi7573a9py2plk2t.png)

Bravo 🎉🎉 !! Our database is up and running, now the only thing left to do is to create a new class method that receives an object (a task) and save it. Let's go to our Database class and create a method called persist, next let's create an IDBTransaction object from the instance of the database and then get access to the object store, like this:

```javascript
export default class Database {
  constructor(name, version, fields) {
   // ...
  }

  persist(task) {
    if (typeof task === "object") {
      const transaction = this.indexedDB.transaction([this.name], "readwrite");
      const objectStore = transaction.objectStore(this.name);
    } else {
      throw new Error("An object was expected.");
    }
  }
}
```
Now, in the previous part, we also talked about a `request` that we have to make through the `objectStore` object, that's because, to save a new record, we need to call the `add()` method on the `objectStore` object, said method will return an `IDBRequest` object with information about the status of our petition. This is useful because we can then pass a callback function to execute when the request is complete. The whole process would look something like this:

```javascript
export default class Database {
  constructor(name, version, fields) {
   // ...
  }

  persist(task, success) {
    if (typeof task === "object") {
      const transaction = this.indexedDB.transaction([this.name], "readwrite");
      const objectStore = transaction.objectStore(this.name);
      const request = objectStore.add(task);
      if (typeof success === "function") request.onsuccess = success;
    } else {
      throw new Error("An object was expected.");
    }
  }
}
```

Great 👏! We are almost done as we still need to supply a callback for when the transaction is complete, we _could_ pass more callbacks to the `persist` function, but it is more practical if we just return the `transaction` object, that way we can handle it in the `index.js` file.

### Using the `persist` Function
Before moving out to the `index.js` file, let's think for a moment what kind of operations we could execute as callbacks for the request and the transaction. 

If a request is _fulfilled_, it does not necessarily mean that everything went fine, it just tells us that our request was accepted, therefore, we can not assure that the object was successfully saved but, we sure can say that the data recollected from the `form` were stored in the `task` object and if that's the case, we can then reset the form.

On the other hand, if a transaction is completed successfully, then there were no errors during the process that was requested and if that's the case, then we can be sure the object was successfully saved.

Now that we have that figured out, we can code it! The implementation would look something like this:

```javascript
function saveTask(event) {
    // ...
  const task = {title, description};
  const transaction = database.persist(task, () => form.reset());
  transaction.oncomplete = () => {
    console.log("Task added successfully!");
  }
}
```
Now to the fun part, let's test it! Go to your browser and create a task you would like to do, in my case, I want to finish all my series on Netflix.

![Screenshot Test Application](https://dev-to-uploads.s3.amazonaws.com/i/9puu3p0622cg850f3j1d.png)

Press on the `Create` button and you will see that the `form` resets itself (as intended), and after a few seconds (or less) you should see the following message on your console:

![Screenshot Console](https://dev-to-uploads.s3.amazonaws.com/i/5w9qtr2przlmhet1nb9s.png)

And if we check again the **Application** tab, we now will see our newly saved data:

![Screenshot Application Tab](https://dev-to-uploads.s3.amazonaws.com/i/xupmis5icyqjld4vhhee.png)

Excellent! The task has been successfully saved.

### Let's Recap
In this second part, we:
- Created the `Database` class to encapsulate the IndexedDB instance
- Learned how we can create fields dynamically in the constructor of the `Database` class
- Learned how we can see the status of our database by checking in the **Application** tab on the Chrome DevTools
- Learned how we can save new objects by using the `add()` method on the `objectStore`
- Learned what to expect when the `request` emits the `onsuccess` event
- Learned what to expect when the `transaction`  emits the `oncomplete` event

The complete code is available in the project repo tagged as `finished code` for part one, go check it out.

{% github DevTony101/js-todo-app-indexed_db no-readme %}

That's all! In the next part, we will begin with the implementation of a function that allows us to retrieve the objects stored in the database and then display them on the page.

Thank you so much for reading! If you have questions or suggestions please leave them down below. See you next time 👋.