Adding Database Capability to Checklist App

Reading Time: 13.27 minutes

Hey guys and welcome to the next part of this course!

This is the continuation of the Checklist App from the previous lessons.

The previous posts guides you through the creation of a web application. But the data in that application get's erased everytime the user refreshes the browser.

In this part, we will add a storage capability for the app. We can do this using the storage of the browser so that the data resides in the client's device or computer even they refreshes it, but I chose a cloud solution so that we carry our data even if we close the browser. What more is that we will be using this type of solution in future lessons.

For now, we will use the Firebase Firestore. It's a product of google and the free tier provides us enough room to create complex databases enough for small apps such as we have.

With all that. let's get started.

NoSQL Database

Firebase Firestore is one of what we call the NoSQL databases. These type of databases don't use the SQL. SQL or Structured Query Language is a programming language used to store and process information to and from a relational database. Relational databases are data stores that holds the data in a table-like form. Some examples are:

  • MySQL
  • PostgresQL
  • MariaDB
  • Oracle Database
  • Microsoft SQL Server
  • SQLite and other more.

NoSQL databases on the other hand uses other query language depending on their data structure. The data on a nosql database has no strict structure. That means you can toss anything in it. That's the flexibility of the storage and you can then create a flexible queries to it so that you get to harness its flexibility.

Creating a Project

Now before we use the Firestore, we need to have an account in Google. So let's open up the website at https://firebase.google.comopen in new window.

firebase website
firebase website

This is the website for firebase. You can use your google account to sign in here. If you don't have one yet, create one now.

On the upper right corner of the page, click on the Go to console button. This will bring us to the firebase console. In this page, all our projects will be displayed.

firebase console
firebase console

To start, click on the Add project button. We will create a firebase project for our checklist app.

You can name the app whatever you want as long as it is still available in the whole firebase list of apps. For me I'll name it to alex-checklist.

new project page
new project page

Make sure to put no spaces in the name. Click on Continue button after. You will then go to the next step page, just leave everything as default. The google analytics option will connect your app to google analytics so that you can track the usage of it.

google analytics option
google analytics option

Just click on Continue. If you leave the Google analytics checked, you will be presented this

google analytics account
google analytics account

Don't bother much about this, just choose the Default Account for Firebase option from the dropdown list and click Create project button. This will now trigger the provisioning of your project.

provisioning firebase project
provisioning firebase project

Just wait for it to complete.

firebase project created
firebase project created

Just click continue to go on to our project dashboard.

firebase dashboard
firebase dashboard

Now it is time to create an app.

Firebase App

In order for use to connect firebase to our app, we need to create an app for it inside the firebase. In this instance, we will create a web app. So click on the web app button like below

select project type
select project type

Now we will get the firebase development kit so that we can connect our app to firebase.

setup firebase sdk
setup firebase sdk

Let's make sure that you do not check the Firebase Hosting. Leave it uncheck. Now click the Register app button.

Select the Use a <script> tag option under the "Add Firebase SDK" step. The code then below it is what we are going to use in our code.

firebase sdk
firebase sdk

Now that is it for now. Make sure to copy the code somewhere. Let us go back to the code of our app from the previous chapters.

We will be working on our javascript file.

At the top of our code, insert the following highlighted code:

// ================================================
// Firebase setting
// Import the functions you need from the SDKs you need
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.10.0/firebase-app.js";
import {
  getFirestore,
  collection,
  addDoc,
  onSnapshot,
  setDoc,
  doc,
} from "https://www.gstatic.com/firebasejs/10.10.0/firebase-firestore.js";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  // ... Paste the configuration from your sdk here...
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
// const analytics = getAnalytics(app);
const db = getFirestore(app);

// ================================================
// End of firebase setting

const buttonAdd = document.getElementById("add-todo");
const todoText = document.getElementById("new-todo");
// ... the rest of the code from the previous app



 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 







From your firebase sdk, paste the content of

firebase config
firebase config

inside the

const firebaseConfig = {
  // ... Paste the configuration code here
};

For now that's it. If successfuly, we have integrated our firebase app with our app.

Now let's create a firestore database.

Set as Module

Upon running the code, open your browser console and you will see that we have an error. The error is saying that

Uncaught SyntaxError: Cannot use import statement outside a module

That means, our javascript code should be a module type so that we can use statements such as import in our javascript code. So let's edit our html code, in the part where we import our javascript file, we will add a type attribute. So instead of

<script src="script.js"></script>

it will be

<script type="module" src="script.js"></script>

Save it and the error should now be gone.

Firebase Firestore

From the previous step, click on Continue to console button.

continue to console
continue to console

Now we will create a firestore database.

On the left side of the firebase console, you will find "Product Categories". Select the "Build" option. It will show us a bunch of things we can build with firebase. Choose "Firestore Database" from the list.

firebase build categories
firebase build categories

The dashboard for the firestore will be like this

firebase firestore
firebase firestore

Now click on the Create database.

On the create database window, select the location nearest to you for faster connection. For me, I will choose Hongkong then click "Next".

create database window
create database window

For the next screen, just choose the Production mode and hit Create.

set production mode
set production mode

This will now provision the cloud firestore.

firestore data dashboard
firestore data dashboard

For now, this is it for our setting up of firestore.

We will now return to our javascript code.

Adding Data to Firestore Database

Since we have already setup the fundamental functions for our app, we have the function for the click event of our button. To get back to the function let's show it

// Function to be called when the button is clicked
function buttonClicked() {
  inputText = inputBox.value; // Put the content of input box to the variable

  // Add item to the array using Array.push method
  items.push({
    name: inputText,
    checked: false,
  });

  renderItems();
}

Here, instead of pushing our new checklist item to the local variable items, we will put it to firestore collection.

A collection like a container in the firestore database that can hold a list of documents. These documents are like json data.

Let's replace the code from lines 6-11.

function buttonClicked() {
  inputText = inputBox.value; // Put the content of input box to the variable

  addDoc(collection(db, "checklist"), {
    name: inputText,
    checked: false,
  }).then((data) => {
    console.log(`Checklist with id ${data.id} created successfully!`);
  });
}

Both addDoc and collection are firestore functions that are given to use and is imported on our setup. db object is the reference to our database and the string "checklist" is the name of our collection.

If the collection exists in our firestore database, this code will add the checklist item as document into that collection, however if the collection "checklist" does not yet exist, as like in our case because we just created an instance of our firebase app, it will create the new collection with the checklist item as it's first document.

On our code above, when the request to database pushes through, it prints something out to the browser console. You may improve this later on by showing a dialog alert to the user. But we will ignore it for now.

Now if you notice, from our original code, we call the renderItems function inside the buttonClicked. This time, we will call the renderItems function only if there is a change in our database. This means that other devices can make changes to our database when we use the app, and our app will automatically update!

In firestore, there is a function called onSnapshot which is fired when there are changes happened in the database collection, depending on what collection we listen to.

We will now add this to our code.

onSnapshot(collection(db, "checklist"), (snapShot) => {
  items = [];

  snapShot.forEach((doc) => {
    const item = {
      id: doc.id,
      ...doc.data(),
    };
    items.push(item);
  });

  renderItems();
});

Here, we are listening to the collection "checklist". First, on line 2, we are clearing the contents or out items array by setting it to []. And then we are using the new value result of snapshot in snapShot value which is an array. The items array will now be populated and the data is coming from the database.

After this, we then call the renderItems function.

With the code above, it should already be working for the saving of our data to database.

Next we will do is to update data in the database when the user check or uncheck an item.

Updating Firestore Document

Since from the previous chapters, we have put an itemClicked function that is called when our checkbox is clicked, we can use it to update the data on the cloud.

The code before is this.

// Call when a checkbox is clicked.
function itemClicked(id) {
  const item = items[id];

  // Change the checked property
  item.checked = !item.checked;

  renderItems();
}

We will change those to this

// Call when a checkbox is clicked.
function itemClicked(id) {
  let selectedItem = null;
  items.forEach((item) => {
    if (item.id === id) {
      selectedItem = item;
    }
  });

  const checklistRefs = collection(db, "checklist");
  setDoc(doc(checklistRefs, id), {
    name: selectedItem.name,
    id: selectedItem.id,
    checked: !selectedItem.checked,
  });

  renderItems();
}

On lines 2-9, we capture the selected item by checking the items array with the "id". If found, is sets the selectedItem variable to the object.

We then update the firestore document with the setDoc function. In this function, we will pass the "id" which the firestore will find from the collection. We will also pass the whole updated object because if we ommit other fields, they will be deleted on the document in the database.

Let's look now at the content of the collection in firebase.

collection preview
collection preview

Now that's it! Our app if finally saving and retrieving data from an online database!

To give you an updates on the, here are the code listing for the html and javascript files.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <!-- Importing of css file -->
    <link rel="stylesheet" href="/style.css" />
    <!-- End of importing css file -->
    <title>Document</title>
  </head>
  <body>
    <h1>Checklist App</h1>

    <div id="checklist-form">
      <input type="text" id="item-text" placeholder="Enter text here..." />
      <button id="add-item">Add Item</button>
    </div>

    <ul id="checklist"></ul>

    <!-- Import javascript file -->
    <script type="module" src="/script.js"></script>
  </body>
</html>
// ================================================
// Firebase setting
// Import the functions you need from the SDKs you need
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.10.0/firebase-app.js";
import {
  getFirestore,
  collection,
  addDoc,
  onSnapshot,
  setDoc,
  doc,
} from "https://www.gstatic.com/firebasejs/10.10.0/firebase-firestore.js";
// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "AIzaSyA63Acz0cHRsRQVKVfclTmxdLWsENSve0U",
  authDomain: "syncster-checklist.firebaseapp.com",
  projectId: "syncster-checklist",
  storageBucket: "syncster-checklist.appspot.com",
  messagingSenderId: "365577317602",
  appId: "1:365577317602:web:cd868f4ece6394ac0082bb",
  measurementId: "G-59FS80N3WF",
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
// const analytics = getAnalytics(app);
const db = getFirestore(app);

// ================================================
// End of firebase setting

const inputBox = document.getElementById("item-text");
const buttonAdd = document.getElementById("add-item");
let inputText = "";

// Create a constant that will hold our list element
const list = document.getElementById("checklist");

// Declare an empty array
let items = [];
let listHtml = "";

buttonAdd.addEventListener("click", buttonClicked);

// Function to be called when the button is clicked
function buttonClicked() {
  inputText = inputBox.value; // Put the content of input box to the variable

  // // Add item to the array using Array.push method
  // items.push({
  //   name: inputText,
  //   checked: false,
  // });

  addDoc(collection(db, "checklist"), {
    name: inputText,
    checked: false,
  }).then((data) => {
    console.log(`Checklist with id ${data.id} created successfully!`);
  });
}

onSnapshot(collection(db, "checklist"), (snapShot) => {
  items = [];

  snapShot.forEach((doc) => {
    const item = {
      id: doc.id,
      ...doc.data(),
    };
    items.push(item);
  });

  renderItems();
});

// Call when a checkbox is clicked.
function itemClicked(id) {
  let selectedItem = null;
  items.forEach((item) => {
    if (item.id === id) {
      selectedItem = item;
    }
  });

  const checklistRefs = collection(db, "checklist");
  setDoc(doc(checklistRefs, id), {
    name: selectedItem.name,
    id: selectedItem.id,
    checked: !selectedItem.checked,
  });

  renderItems();
}

// The render function to render the list
function renderItems() {
  listHtml = "";

  for (let i = 0; i < items.length; i++) {
    let className = "";

    if (items[i].checked) {
      className = "completed";
    }

    listHtml += `<li>
      <input type='checkbox' id='${items[i].id}'>
        <label for='${items[i].id}' class='${className}'>
          ${items[i].name}
        </label>
      </li>`;
  }

  list.innerHTML = listHtml;

  for (let i = 0; i < items.length; i++) {
    let checked = items[i].checked;

    document.getElementById(items[i].id).checked = checked;

    // Add eventlistener
    document.getElementById(items[i].id).addEventListener("click", function () {
      itemClicked(items[i].id);
    });
  }
}

Update Firestore Rule

Now that we have completed our code and when you run the app, we might get a permission denied error when we look at the browser console. This is due to the setting in our firestore rules.

Now go back to the firebase firestore console.

Firestore rules
Firestore rules

Select the "Rules" tab from above and we will see the code for the rule that is set by default in production.

And it is something like this.

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if false;
    }
  }
}

Just change this line

allow read, write: if false;

to this

allow read, write: if true;

This will allow all requests from the clients and should be refined later when you are already in production. But for now, this should work.

Notice that when changing from false to true, you will get some suggestions as below.

Change rule
Change rule

After changing the code, hit the "Publish" button and our new rule will be published.

For the next chapter, we will deploy our app to the cloud so that it can be accessed anywhere.

Thanks for reaching this point. For any questions, just leave a comment.

Last Updated:
Contributors: alexiusacademia