How to create a TODO application creation with Express.js

overview

Express v4.17.1
nodejs v19.7.0

The first part uses Express.js to build the API, and the second part uses HTML and JavaScript to create the front end, creating a complete TODO application.

Express.js v4.17.1
Node.js v16.3.0

In addition, all the code created this time is posted on GitHub.

  1. API endpoints:

  2. GET /todos Get all TODOs

  3. POST /todos create a new TODO

  4. PUT /todos/:id to update an existing TODO

  5. DELETE /todos/:id Delete an existing TODO

  6. Front-end features:

  7. Display the TODO list

  8. Create a new TODO

  9. Update existing TODO

  10. Remove existing TODOs

How to create a TODO app backend

server.js

// import the module
const express = require("express");
const app = express();
const path = require('path');

// Specify the directory that hosts the HTML files.
app.use(express.static(path.join(__dirname, 'public')));

// create a todo for the list
let todos = [
  { id: 1, text: "Play baseball", completed: false },
  { id: 2, text: "Play a game", completed: false },
  { id: 3, text: "work", completed: false },
];

app.use(express.json());

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

// get list of todos with '/todos' path
app.get("/todos", (req, res) => {
  res.json({ todos });
});

// Create a new Todo by sending a POST request to the "/todos" path
app.post("/todos", (req, res) => {
  const newTodo = req.body;
  newTodo.id = todos.length + 1;
  push(newTodo);
  res.json({ todo: newTodo });
});

// update a todo by sending a PUT request to the "/todos/:id" path
app. put("/todos/:id", (req, res) => {
  const id = req.params.id;
  let todo = todos.find((t) => t.id == id);
  if (todo) {
    Object.assign(todo, req.body);
    res.json({ todo });
  } else {
    res.status(404).send({ message: "Todo not found" });
  }
});

// delete a todo by sending a DELETE request to the "/todos/:id" path
app.delete("/todos/:id", (req, res) => {
  const id = req.params.id;
  const index = todos.findIndex((t) => t.id == id);
  if (index !== -1) {
    todos.splice(index, 1);
    res.json({ message: "Todo deleted" });
  } else {
    res.status(404).send({ message: "Todo not found" });
  }
});

// listen to the server on port 3000
app.listen(3000, () => {
  console.log(`server is listening on port 3000`);
});

Commentary

// import the module
const express = require("express");
const app = express();
const path = require('path');

The above code imports a module called Express and a module called path. The app object created by calling the express() function contains methods for handling HTTP requests.

// Specify the directory that hosts the HTML files.
app.use(express.static(path.join(__dirname, 'public')));

Here, we set middleware for serving static files (HTML, CSS, JavaScript, image files, etc.). This middleware serves files in the specified directory directly via HTTP.

app.use(express.json());

This is a piece of middleware that allows the server to parse POST requests with JSON formatted bodies.

app.get('/', (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

This code defines the response for a GET request to the root path (/). In response, the server returns the index.html file in the public directory.

app.get("/todos", (req, res) => {
  res.json({ todos });
});

This part configures handling of GET requests to the /todos path. This will return a todos array in JSON format when a request to this path is made.

app.post("/todos", (req, res) => {
  const newTodo = req.body;
  newTodo.id = todos.length + 1;
  push(newTodo);
  res.json({ todo: newTodo });
});

This part handles POST requests to the /todos path. Create a new TODO item and add it to the todos array. Then return the new TODO items to the client in JSON format.

app. put("/todos/:id", (req, res) => {
  const id = req.params.id;
  let todo = todos.find((t) => t.id == id);
  if (todo) {
    Object.assign(todo, req.body);
    res.json({ todo });
  } else {
    res.status

(404).send({ message: "Todo not found" });
  }
});

This part handles a PUT request to the /todos/:id path. Find the TODO item with the given id and update it with the body of the request. It then returns the updated TODO items to the client in JSON format.

app.delete("/todos/:id", (req, res) => {
  const id = req.params.id;
  const index = todos.findIndex((t) => t.id == id);
  if (index !== -1) {
    todos.splice(index, 1);
    res.json({ message: "Todo deleted" });
  } else {
    res.status(404).send({ message: "Todo not found" });
  }
});

This part handles DELETE requests for the /todos/:id path. Find the TODO item with the specified id and remove it from the todos array. It then returns a message in JSON format to the client indicating that the deletion was successful.

// listen to the server on port 3000
app.listen(3000, () => {
  console.log(`server is listening on port 3000`);
});

Finally, the server will listen on port 3000. This will cause the server to handle all HTTP requests received on port 3000.

How to create a frontend for a TODO app

Next, create the front end part. Save the HTML and JavaScript below in a file named index.html.

<!DOCTYPE html>
<html>
  <head>
    <title>TODO App</title>
    <style>
      /* Write your CSS here */
    </style>
  </head>
  <body>
    <h1>TODO App</h1>
    <input id="todo-input" type="text" placeholder="Enter new TODO" />
    <button id="add-todo">Add</button>
    <ul id="todo-list"></ul>
    <template id="todo-item-template">
      <li>
        <span class="todo-text"></span>
        <button class="delete-todo">Delete</button>
        <button class="update-todo">Done</button>
      </li>
    </template>
    <script>
      const todoInput = document.querySelector("#todo-input");
      const addTodoButton = document.querySelector("#add-todo");
      const todoList = document.querySelector("#todo-list");
      const todoItemTemplate = document.querySelector("#todo-item-template");

      // Get all TODOs and display them on the screen
      function fetchTodos() {
        fetch("http://localhost:3000/todos")
          .then((response) => response.json())
          .then((data) => {
            todoList.innerHTML = "";
            data.todos.forEach((todo) => {
              const todoItem = todoItemTemplate.content.cloneNode(true);
              const textElement = todoItem.querySelector(".todo-text");
              const deleteButton = todoItem.querySelector(".delete-todo");
              const updateButton = todoItem.querySelector(".update-todo");

              textElement.textContent = todo.text;
              updateButton.textContent = todo.completed ? "not completed" : "completed";

              deleteButton.addEventListener("click", (event) => {
                event.stopPropagation();
                deleteTodo(todo);
              });

              updateButton.addEventListener("click", (event) => {
                event.stopPropagation();
                updateTodo(todo);
              });

              todoList.appendChild(todoItem);
            });
          });
      }

      // create a new TODO
      function addTodo() {
        const newTodo = { text: todoInput.value, completed: false };
        fetch("http://localhost:3000/todos", {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(newTodo),
        }).then(() => {
          fetchTodos();
          todoInput.value = "";
        });
      }

      // update TODO
      function updateTodo(todo) {
        todo.completed = !todo.completed;
        fetch(`http://localhost:3000/todos/${todo.id}`, {
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(todo),
        }).then(fetchTodos);
      }

      // remove TODO
      function deleteTodo(todo) {
        fetch(`http://localhost:3000/todos/${todo.id}`, {
          method: "DELETE",
        }).then(fetchTodos);
      }

      addTodoButton.addEventListener("click", addTodo);
      fetchTodos();
    </script>
  </body>
</html>

Commentary

The HTML part uses <template> tags to define the layout of the TODO items.

This is a hidden element that you use to clone and display its contents with JavaScript.

<template id="todo-item-template">
  <li>
    <span class="todo-text"></span>
    <button class="delete-todo">Delete</button>
    <button class="update-todo">Done</button>
  </li>
</template>

Elements in this template are assigned class names so that they can be referenced from JavaScript.

const todoItem = todoItemTemplate.content.cloneNode(true);
const textElement = todoItem.querySelector(".todo-text");
const deleteButton = todoItem.querySelector(".delete-todo");
const updateButton = todoItem.querySelector(".update-todo");

To generate a new TODO item, clone the content of the template (.content.cloneNode(true)) and customize the text and buttons. Each button adds an event listener that performs the corresponding operation (delete or update).

Closing

In this article, I showed you how to create a backend API using Express.js and then use it to create a frontend TODO app in HTML and JavaScript. We’ve built a very basic application here, but you can build on it to build a more complex application.