Skip to content
Related Articles

Related Articles

How to make to do list using Nodejs ?

View Discussion
Improve Article
Save Article
  • Last Updated : 24 Mar, 2022

A To-do list is a beginner application that many programming students make. It is a list of tasks that you need to do in a day. You can add tasks to it and even delete them when done with them. Often it can be seen that many tutorials leave the to-do list application on the frontend part only. The reader never gets to know how to integrate it with the backend, such that a proper database is used and data is not lost on refresh/restart. This article seeks to do teach you how to make a complete To-do List project.

This article is targeted at the audience that has gained basic knowledge of web development but has not built any projects. Many students know the syntax but are unable to make applications. Just knowing certain technology such as Nodejs is not enough, being able to use it with other technologies and build an application using programming logic is also required. This article is made to help such people sharpen their knowledge by building projects.

Features of our application:

  • Dynamic Frontend, through EJS, which is an NPM package.
  • Backend built with Nodejs.
  • Database Used: MongoDB Atlas.

How the application works:

  • A home page is rendered where the user can see his tasks and also insert a new task.
  • A new request is sent to the server-side when we click on add button.
  • A new request for deleting the item is sent when we check it, once the task is completed.

Note: This article will focus mainly on the backend, thus detailed explanations for the frontend part, i.e., HTML and CSS will not be given, although code will be provided. 

Let’s start with step by step implementation.

Step 1: Open an empty folder in VS Code. Create a file with the name index.js inside this folder. 

Step 2: Write the following command in the terminal, to initialize this folder for making a Nodejs project.

npm init

Step 3: After this, write the following command to install some packages that we will be using in our application:

npm install –save express ejs body-parser mongodb mongoose

Explanation: The above commands install the required packages, which are required in our application.

  • ejs for rendering content on frontend.
  • express is a Nodejs framework used to help in code redundancy.
  • body-parser for reading data from incoming requests.
  • mongodb for being able to use databases and mongoose is its framework for code redundancy and easy connection.

Now create two folders beside our app.js, Name them as public ( for files we want to display to the user ) and views( for EJS files ).

Project Structure: It will look like the following.

Step 4: Now open your index.js file. In this file, we are going to code our server. Where we handle the requests that come from the browser, manage the data in them and respond accordingly. We will deal with two types of requests, which are the most used, get and post. Get request is for reading from server and post request in writing to the server. We will also define on which port of our machine the server this application’s server is accessed. This is the file where all of our application’s logic exist. We even connect to a cloud database in this file.

index.js




// To use the packages installed, we import 
// them using require and save them in
// a constant
const express = require("express");
const bodyParser = require("body-parser");
const ejs = require("ejs");
const mongoose = require("mongoose");
  
// Initializing a constant to use express 
// methods and create middlewares.
const app = express();
  
// Telling Node.js to use body-parser for
// reading data coming from our 
// incoming requests in URL
app.use(bodyParser.urlencoded({ extended: true }));
  
// Telling Nodejs that all our static 
// files(here: CSS files) are 
// stored in public folder
app.use(express.static("public"));
  
// Telling Nodejs that we will write our
// frontend in ejs files. Thus viewing
// engine has to be set to use ejs
app.set("view engine", "ejs");


Explanation: We include the installed packages in some constants to use in our application. app is the constant through which we create our middleware and initialize packages for use in our application. First, body-parser is initialized to tell Nodejs to use body-parser for reading data. the public folder is declared static to tell Nodejs that this is where our static files (e.g. CSS and images )are located. And at last, ejs is set as a view engine so that we can write our frontend code in ejs files.

Note: We don’t require mongodb, as it is taken care of by mongoose.

Step 5: Now we are going to connect our application with the cloud database, MongoDB Atlas and define the basic structure of our collection. We define what is the type of data we store and other features associated with it.

index.js




// Make sure you did not use any special
// characters(e.g. @) in your user-name
// or password
mongoose.connect(
"mongodb+srv://<name>:<password>@cluster0.38u1b.mongodb.net/todoDB");
  
// Defining the schema or structure of
// a single item in mongodb
const taskSchema = {
    name: {
        type: String,
        required: true
    }
};
  
// Using the following code, node.js 
// creates a collection named 
// 'tasks' using the taskSchema
const Task = mongoose.model("Task", taskSchema);


Explanation: Here we use our mongoose constant to connect to our database through passing the link to connect method on constant object mongoose. Then we describe the blueprint of our item in mongodb, where we set its data type as String. The model method uses this blueprint to create a collection with the name items. We need to write a singular word with a title case as shown, mongoose automatically converts it to a small case and a plural word. The word “todoDB” at the end of our database URL is the name I give to the database. You can use any other word if you want to.

The URL for our database is received through the following steps from our MongoDB Atlas account:

  • Click on the Connect Button.

  • Choose the second Option: Connect your Application.

  • Copy the URL Format.

Step 6:  Now we work on our requests, The first request we work on is the request for our home page, sent to URL: “/”. Here we write the code where we serve our home page to the route or path of our home page, which is “/” for any website. The home page is simply showcasing all our tasks. In this application, it is the only webpage we have, as there is no need to have any other page.

index.js




app.get("/", function (req, res) {
  
    // Getting today's date to display
    // on top of our to-do
    let today = new Date();
    let options = { 
        weekday: "long"
        day: "numeric"
        month: "long" 
    };
      
    // If we do not use the first argument
    // in below line, which is "en-US" we get
    // date in form of numbers only, separated
    // with a /, thus the day won't be known
    let day = today.toLocaleDateString("en-US", options);
  
    // Find is a function given by mongoose, which
    // is applied to a collection, it returns
    // all the documents found in it.
    Task.find({}, function (err, foundTasks) {
        if (err) {
            console.log(err)
        }
        else {
  
            // Render the file names index.ejs and
            // send the object to with the following
            // data, sent as second parameter, in
            // which we send date 
            // and tasks found in database.
            res.render("index", { today: day, tasks: foundTasks });
        }
    })
});


Explanation: The get request is for reading data, when the URL of our application is typed in the browser, the get request is sent to the “/” route, which is our home page for reading and viewing contents of our home page. app.get() tells that it is to be run when a get request is received on “/”, by running the callback inside it. find is a method defined by mongoose on each collection to find documents in that collection. An empty object is passed to this find function to tell that there is no condition that the found document has to match, thus it signifies, that all documents are to be fetched. The result this function returns if no error is found is an array of objects.  On the response object, we call the render function that takes the name of ejs file to send, and an object of values to send to the frontend.

Step 7: Now let’s go to our “views” folder and create an ejs file, index.ejs, Here we define the structure of our webpage, using HTML code. We also write some Javascript logic that maintains the dynamic rendering of our application. This dynamic rendering is simply showing the newly added task on our page without having the user reload the page.

index.ejs




<!DOCTYPE html>
<html lang="en">
  
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible"
        content="IE=edge">
    <meta name="viewport" content=
        "width=device-width, initial-scale=1.0">
    <title>To-do List</title>
    <link rel="stylesheet" href="css/styles.css">
</head>
  
<body>
    <div class="box" id="heading">
        <h1 style="color: #ffffff">
            <%= today %>
        </h1>
    </div>
  
    <div class="box">
  
        <!-- We check if the object receieved is not
            empty, else there will be an error -->
  
        <!-- To avoid error, we simply use if-else, 
            empty object is handled in if block -->
        <% if (tasks.length===0) {%>
            <p style="text-align: center;">
                No Task Added Yet
            </p>
  
        <% } else { %>
            <% for (var i=0; i< tasks.length; i++){ %>
  
                <!-- Action is the route to which form 
                    send request -->
                <!-- And method defines the "type" of request -->
                <form action="/delete" method="post">
                    <div class="item">
                        <input type="checkbox" name="checkbox" 
                            value="<%=tasks[i]._id%>"
                            onchange="this.form.submit()">
                        <p>
                            <%= tasks[i].name %>
                        </p>
                    </div>
                    <hr>
                </form>
            <% } %>
        <% } %>
                  
        <!-- Action is the route to which form 
                                send request -->
        <!-- And method defines the "type" 
                                of request -->
        <form class="item " action="/" method="POST">
            <input type="text" name="newTask" 
                autocomplete="off" 
                placeholder="Add a New Task Here">
              
            <button type="submit" name="submit">+</button>
        </form>
    </div>
</body>
  
</html>


Explanation: The syntax of ejs is similar to HTML. The Javascript statements are written within <%  %> and variables are written within <%=  %>. This ability to use Javascript with HTML allows ejs to have a dynamic rendering feature. In this ejs file, we first check if the array received is empty or not, if it is not empty, then we loop through this array of objects and use a dot operator on each object to display its name. We also place a checkbox beside the name of the item inside a form that sends a post request on the “/delete” route, and set its value to the id of the object, which is provided by MongoDB. The id is written as _id in MongoDB. We want that a request is sent when we check the checkbox, thus the form is made to submit using submit function.

NOTE: Make sure that the name of the variable matches the key of the object sent to the frontend from Nodejs while rendering ejs.

Step 8: In styles.css file, we write our CSS code, which gives a good look to our homepage. It makes the main container and heading of our page appear in the center, we define color themes we want to use and also style our font, button, and other elements on our page.

styles.css




*{
  font-family: cursive;
  box-sizing: border-box;
}
h1 {
  padding: 10px;
}
  
.box {
  max-width: 450px;
  margin: 20px auto;
  background: white;
  border-radius: 5px;
  box-shadow: 7px 7px 15px 5px rgba(0, 0, 0, 0.3);
}
  
#heading {
  background-color: #353434;
  text-align: center;
}
  
.item {
  min-height: 70px;
  display: flex;
  align-items: center;
  border-bottom: 1px solid #F1F1F1;
}
  
.item:last-child {
  border-bottom: 0;
}
  
input:checked+p {
  text-decoration: line-through;
  text-decoration-color: #353434;
}
  
input[type="checkbox"] {
  appearance: none;
  margin: 20px;
  height: 25px;
  width: 25px;
  border: 2px solid black;
  border-radius: 3px;
}
input[type="checkbox"]:hover,
input[type="checkbox"]:focus{
  transform: scale(1.2);
  background-color: #353434;
  color: white;
  cursor: pointer;
    
}
input[type="checkbox"]:checked{
  clip-path: polygon(14% 44%, 0 65%
    50% 100%, 100% 16%, 80% 0%, 43% 62%);
}
  
p {
  text-align: left;
  margin: 0;
  padding: 20px;
  font-size: 1.8rem;
  font-weight: bold;
  color: #353434;
  text-shadow: 2px 2px gray;
}
  
form {
  text-align: center;
  padding: 10px;
}
  
button {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  font-size: 1.5rem;
  background-color: #494848;
  border-top: 4px solid white;
  border-left: 4px solid white;
  border-bottom: 4px solid black;
  border-right: 4px solid black;
  color: white;
}
button:hover{
  cursor: pointer;
  color: #494848;
  background-color: white;
  border: 3px solid #494848;
  box-shadow: 4px 4px 5px rgba(0, 0, 0, 0.459);
}
input[type="text"] {
  margin: 5px;
  text-align: center;
  height: 50px;
  background: transparent;
  font-size: 20px;
  font-weight: 200;
  width: 100%;
  border: none;
  border-bottom: 4px solid #494848;
}
  
input[type="text"]:focus {
  outline: none;
  border: 2px solid #494848;
  border-bottom: 4px solid #494848;
  border-radius: 5px;
  box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.301);
}


Step 9: Now we work on the routes to add an item. We are back to our index.js file. Now we will write the code that reads incoming data from the frontend, saves it in the database as a new task, then redirects back to the home page once the task is successfully added. We also check if the incoming data, here task, is not empty.

index.js




app.post("/", function (req, res) {
    const taskName = req.body.newTask;
    if (taskName) {
        const task = new Task({
            name: taskName,
        });
  
        // Save the task using save method provided 
        // by mongoose. It returns a promise, in 
        // which we re-direct to home page. we write
        // it in then block to make sure that 
        // we are redirected only when the save
        // method finished executing without any 
        // error. Otherwise the item will be saved,
        // after we were redirected, thus, it will look
        // like the task was not added and thus we
        // will have to reload to see the newly added
        // task. Which can be exhausting.
        task.save()
            .then(() => {
                res.redirect("/");
            });
    } else {
        res.redirect("/");
    }
});


Explanation: Just like we make an object from a class, we make a document from the collection using the new keyword. Then we call the save() method on the constant itemName, which saves it to our database. It is read using the request object, then accessing its body and then name of the input using dot operator just like accessing nested objects. This function is also provided by the mongoose. We are also checking if the name of the item is empty or not.  If it is empty, we redirect to home and if not, we save it and then redirect to home. Then again the request of the homepage, which is “/” is received by Nodejs and it runs the middleware for it. This time a new item is found and thus the array it sends to the frontend is updated which results in our page update. This is how dynamic rendering is established in our application.

Step 10: And lastly, we work on our request for delete which is sent each time we click on the checkbox. The page will reload when we click on a checkbox, has in the frontend it is written inside a form, which is submitted automatically once a checkbox is submitted, because of the function passed to it, and send the id of the task it is placed next to. The following code reads the id and will find the task from the database which has this id and delete it, and redirect back to the home page once the task is completed.

index.js




app.post("/delete", function (req, res) {
  const checkedItemId = req.body.checkbox;
  Task.findByIdAndRemove(checkedItemId, function (err) {
    if (!err) {
      console.log("Successfully deleted checked item.");
      res.redirect("/");
    }
  });
});


Explanation: In the action attribute in our form, we used “/delete” as the address to which the request is sent and the method attribute’s value is set as “POST”. Inside this form is the checkbox which submits the form when we click on it. Now, this request with post method is received in app.post(). We store the id of the object which is sent from the frontend, then on items collection, we call a method provided by mongoose to find a document that has the same id as the one received and delete it. It takes an id and a callback as arguments. The callback is run when the item is deleted.

Step 11: Finally, we write a code that makes our application accessible from one of the ports of the machine. It is called listening.

Javascript

app.listen(process.env.PORT || 3000, function () {
console.log(“Server running at port 3000”);
});

Explanation: This is the code we write at the last. listen method is called on our app object to listen to a port on our computer. Here we use 3000, you can use some other port as well. process.env.PORT is the port that the service uses on which our application is hosted. The port they will use doesn’t need to be the same as we were using during our development.

Now let’s see the complete code.

index.js




const express = require("express");
const bodyParser = require("body-parser");
const ejs = require("ejs");
const mongoose = require("mongoose");
  
const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));
  
mongoose.connect(
"mongodb+srv://<username>:<password>@cluster0.g6nae.mongodb.net/todolistDB");
const taskSchema = {
  name: {
    type: String,
    required: true
  }
};
  
const Task = mongoose.model("Task", taskSchema);
  
app.set("view engine", "ejs");
  
app.get("/", function (req, res) {
  
  let today = new Date();
  let options = { weekday: "long", day: "numeric", month: "long" };
  let day = today.toLocaleDateString("en-US", options);
  
  Task.find({}, function(err, foundTasks){
    if (err){
      console.log(err)
    }
     else {
      res.render("index", { today: day, tasks: foundTasks });
    }
  })
  
});
  
app.post("/", function (req, res) {
  const taskName = req.body.newTask;
  if(taskName){
    const task = new Task({
      name: taskName,
    });
    task.save().then(()=>{
      res.redirect("/");
    });
  } else{
    res.redirect("/");
      
  }
});
  
app.post("/delete", function (req, res) {
  const checkedItemId = req.body.checkbox;
  Task.findByIdAndRemove(checkedItemId, function (err) {
    if (!err) {
      console.log("Successfully deleted checked item.");
      res.redirect("/");
    }
  });
});
  
app.listen(process.env.PORT || 3000, function () {
  console.log("Server running at port 3000");
});


Step to run the application: To run the application, open the terminal and write the command.

node index.js

Output: Open browser and in its URL address box, write: localhost:3000.

Now let’s understand how to deploy the above-created app.

Step 1: Go to the website of Heroku. http://www.heroku.com/

Step 2: Sign Up if you don’t already have an account.

Step 3: Fill in the SignUp Form

After Sing Up is complete, you will see a page like this:

You get a Get Started Page:

Step 4: Install Heroku CLI according to your Operating System:

Step 5: Open CLI of your system, or VS Code terminal in the current project directory. Login to Heroku using the terminal command:

heroku login

Step 6: A browser window opens, click on the login button and you are done.

Note: Since we just created an account and have Heroku open in our browser, it recognizes us and does not ask for credentials to log in. If we were logged out from the browser as well, then it would ask for email and password and then log us in.

Step 7: Then initialize git in our project. (make sure you have git installed in your computer system: https://git-scm.com/downloads): Write the command.

git init

Step 8: Add a .gitignore file and a Procfile using the following commands:

touch .gitignore

.gitignore file is created to list all the files and folders that should not be included in our repository. These are mainly node_modules folder, as it can be created anywhere by writing the command: npm install. This command reads the packages.json file and packages-lock.json file which hold the information of packages installed and installs in the directory automatically.

Step 9: Now in the terminal, write the following command.

heroku create

git add .

git commit -m “Initial Commit”

git push heroku master

Step 10: A URL will appear, that is the URL of your hosted application.

Link of deployed application:  https://thawing-ravine-87998.herokuapp.com/


My Personal Notes arrow_drop_up
Recommended Articles
Page :

Start Your Coding Journey Now!