Open In App

How to Make to do List using Nodejs ?

Last Updated : 18 Jun, 2024
Comments
Improve
Suggest changes
Like Article
Like
Report

Creating a to-do list application using Node.js involves setting up an Express server, creating RESTful APIs for CRUD operations, and using a database to store tasks. Enhance functionality with features like task prioritization and deadlines.

Prerequisites:

Features

  • 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.

Steps to Implement To-Do List using Node

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

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.

The updated dependencies in the package.json file

"dependencies": {
    "body-parser": "^1.20.2",
    "ejs": "^3.1.10",
    "express": "^4.19.2",
    "mongodb": "^6.7.0",
    "mongoose": "^8.4.3"
}

Step 4: In index.js, code the server to handle GET and POST requests, manage data, define the server port, connect to a cloud database, and contain all application logic.

Node
// 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");

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.

Node
// 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: Use Mongoose to connect to the database, define an item blueprint with String type, and create a collection named "items." The "todoDB" in the URL is the database name, customizable as desired.

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:  We handle the request for our homepage at URL "/". This code serves our homepage, displaying all tasks. In this application, it's the only webpage needed, showcasing all tasks.

Node
// 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: When our application URL is typed in the browser, a GET request is sent to the "/" route, reading and viewing the homepage content. `app.get()` runs the callback for GET requests on "/". The `find` method from Mongoose retrieves all documents in the collection. If successful, it returns an array of objects. We then call the `render` function on the response object to send an EJS file and values to the frontend.

Step 7: Create an `index.ejs` file in the "views" folder. This file defines the webpage structure using HTML. It includes JavaScript logic for dynamic rendering, allowing new tasks to appear without reloading the page.

HTML
<!-- 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: EJS syntax resembles HTML but allows JavaScript within <% %> and variables in <%= %>. It enables dynamic rendering by looping through received arrays to display items with checkboxes for deletion using MongoDB's _id.

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.

CSS
/* 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.

Node
// 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:In this process, we create a document from a collection akin to making an object from a class. Using `new`, we instantiate and `save()` the `itemName` to our MongoDB database. Conditional checks ensure the item isn't empty; upon saving, a redirect refreshes the homepage, triggering dynamic updates through Node.js middleware.

Step 10: For the delete request, clicking a checkbox triggers a form submission, sending the task's ID to delete. Node.js reads this ID, finds and deletes the corresponding task from the database, then redirects to the homepage upon completion.

Node
// 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 form's action attribute, we specify "/delete" as the endpoint for the POST request. When the checkbox is clicked, the form submits, triggering app.post(). Using Mongoose, we find and delete the document matching the received ID from the items collection, executing a callback upon deletion.

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

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

Explanation: At the end, we use app.listen() to start our server on port 3000. Alternatively, process.env.PORT accommodates the hosting service's port. It needn't match our development port.

Now let's see the complete code.

Node
// 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.

Steps to Deploy Do-To Application

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/


Next Article

Similar Reads