Lesson 48 of 50 – To-Do App Project
96% Completed
96%

Build a To-Do App Using Node.js

In this practical project, you will build a To-Do REST API using Node.js and Express. You will learn how to create tasks, display tasks, update tasks, and delete tasks.

Project Goal: Build a working API that manages daily tasks using CRUD operations.

1. What Is a To-Do App?

A To-Do application helps users manage tasks such as studying, completing assignments, attending classes, and finishing projects.

Our application will support these features:

  • Add a new task.
  • Display all tasks.
  • Display a single task.
  • Update a task.
  • Mark a task as completed.
  • Delete a task.

2. Understand CRUD Operations

Operation HTTP Method Endpoint Purpose
Create POST /api/tasks Add a task
Read All GET /api/tasks Display all tasks
Read One GET /api/tasks/:id Display one task
Update PUT /api/tasks/:id Update a task
Delete DELETE /api/tasks/:id Remove a task

3. Create the Project

Open your terminal and execute these commands:

mkdir todo-project
cd todo-project
npm init -y
npm install express

Create a file named server.js.

Project Structure

todo-project/
│
├── node_modules/
├── package.json
├── package-lock.json
└── server.js

4. Create the Express Server

First, create a basic Express server and enable JSON request bodies.

const express = require('express');

const app = express();

app.use(express.json());

const PORT = process.env.PORT || 3000;

app.get('/', (req, res) => {
    res.send('Welcome to the To-Do App API!');
});

app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});

Run the Server

node server.js

Open this URL in your browser:

http://localhost:3000/

Expected output:

Welcome to the To-Do App API!

5. Store Tasks in an Array

For this beginner project, we will store tasks in a JavaScript array. Each task will have an ID, title, description, completion status, and creation date.

let tasks = [
    {
        id: 1,
        title: 'Learn Node.js',
        description: 'Study Express and REST APIs',
        completed: false,
        createdAt: new Date().toISOString()
    }
];

let nextId = 2;
This project uses temporary in-memory storage. All tasks are lost when the server restarts. A database is needed for permanent storage.

6. Complete To-Do App Code

Replace the contents of server.js with the following complete code.

server.js – Complete CRUD API
const express = require('express');

const app = express();

app.use(express.json());

const PORT = process.env.PORT || 3000;

// Temporary in-memory task storage
let tasks = [
    {
        id: 1,
        title: 'Learn Node.js',
        description: 'Study Express and REST APIs',
        completed: false,
        createdAt: new Date().toISOString()
    }
];

let nextId = 2;

// Home route
app.get('/', (req, res) => {
    res.send('Welcome to the To-Do App API!');
});

// GET: Display all tasks
app.get('/api/tasks', (req, res) => {
    res.json(tasks);
});

// GET: Display a single task
app.get('/api/tasks/:id', (req, res) => {
    const id = Number(req.params.id);

    if (!Number.isInteger(id) || id < 1) {
        return res.status(400).json({
            message: 'Invalid task ID'
        });
    }

    const task = tasks.find(task => task.id === id);

    if (!task) {
        return res.status(404).json({
            message: 'Task not found'
        });
    }

    res.json(task);
});

// POST: Create a new task
app.post('/api/tasks', (req, res) => {
    const { title, description } = req.body;

    if (
        typeof title !== 'string' ||
        !title.trim()
    ) {
        return res.status(400).json({
            message: 'A task title is required'
        });
    }

    if (
        description !== undefined &&
        typeof description !== 'string'
    ) {
        return res.status(400).json({
            message: 'Description must be a string'
        });
    }

    const task = {
        id: nextId++,
        title: title.trim(),
        description: description ? description.trim() : '',
        completed: false,
        createdAt: new Date().toISOString()
    };

    tasks.push(task);

    res.status(201).json({
        message: 'Task created successfully',
        task
    });
});

// PUT: Update a task
app.put('/api/tasks/:id', (req, res) => {
    const id = Number(req.params.id);

    if (!Number.isInteger(id) || id < 1) {
        return res.status(400).json({
            message: 'Invalid task ID'
        });
    }

    const task = tasks.find(task => task.id === id);

    if (!task) {
        return res.status(404).json({
            message: 'Task not found'
        });
    }

    const { title, description, completed } = req.body;

    if (
        title !== undefined &&
        (typeof title !== 'string' || !title.trim())
    ) {
        return res.status(400).json({
            message: 'Title must be a non-empty string'
        });
    }

    if (
        description !== undefined &&
        typeof description !== 'string'
    ) {
        return res.status(400).json({
            message: 'Description must be a string'
        });
    }

    if (
        completed !== undefined &&
        typeof completed !== 'boolean'
    ) {
        return res.status(400).json({
            message: 'Completed must be true or false'
        });
    }

    if (title !== undefined) {
        task.title = title.trim();
    }

    if (description !== undefined) {
        task.description = description.trim();
    }

    if (completed !== undefined) {
        task.completed = completed;
    }

    res.json({
        message: 'Task updated successfully',
        task
    });
});

// PATCH: Mark a task as completed or incomplete
app.patch('/api/tasks/:id/complete', (req, res) => {
    const id = Number(req.params.id);

    if (!Number.isInteger(id) || id < 1) {
        return res.status(400).json({
            message: 'Invalid task ID'
        });
    }

    const task = tasks.find(task => task.id === id);

    if (!task) {
        return res.status(404).json({
            message: 'Task not found'
        });
    }

    if (typeof req.body.completed !== 'boolean') {
        return res.status(400).json({
            message: 'Send completed as true or false'
        });
    }

    task.completed = req.body.completed;

    res.json({
        message: 'Task status updated successfully',
        task
    });
});

// DELETE: Delete a task
app.delete('/api/tasks/:id', (req, res) => {
    const id = Number(req.params.id);

    if (!Number.isInteger(id) || id < 1) {
        return res.status(400).json({
            message: 'Invalid task ID'
        });
    }

    const taskIndex = tasks.findIndex(
        task => task.id === id
    );

    if (taskIndex === -1) {
        return res.status(404).json({
            message: 'Task not found'
        });
    }

    const deletedTask = tasks.splice(taskIndex, 1)[0];

    res.json({
        message: 'Task deleted successfully',
        task: deletedTask
    });
});

// Handle invalid JSON and other errors
app.use((err, req, res, next) => {
    if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
        return res.status(400).json({
            message: 'Invalid JSON request body'
        });
    }

    console.error('Unexpected server error');

    res.status(500).json({
        message: 'Internal server error'
    });
});

// Handle unknown routes
app.use((req, res) => {
    res.status(404).json({
        message: 'Route not found'
    });
});

app.listen(PORT, () => {
    console.log(`Server running at http://localhost:${PORT}`);
});

7. Test the API Using Postman

Start the server, open Postman, and send HTTP requests to the following endpoints.

A. Display All Tasks

Method: GET

http://localhost:3000/api/tasks

Example response:

[
    {
        "id": 1,
        "title": "Learn Node.js",
        "description": "Study Express and REST APIs",
        "completed": false,
        "createdAt": "2026-01-01T10:00:00.000Z"
    }
]

The date above is illustrative; the actual creation date will be generated when the application runs.

B. Add a New Task

Method: POST

http://localhost:3000/api/tasks

In Postman, select Body → raw → JSON and enter:

{
    "title": "Complete JavaScript Assignment",
    "description": "Practice Express CRUD operations"
}

Example response:

{
    "message": "Task created successfully",
    "task": {
        "id": 2,
        "title": "Complete JavaScript Assignment",
        "description": "Practice Express CRUD operations",
        "completed": false,
        "createdAt": "2026-01-01T10:00:00.000Z"
    }
}

C. Display One Task

Method: GET

http://localhost:3000/api/tasks/1

D. Update a Task

Method: PUT

http://localhost:3000/api/tasks/1

Send this JSON body:

{
    "title": "Learn Node.js and Express",
    "description": "Practice REST API development",
    "completed": true
}

E. Mark a Task as Completed

Method: PATCH

http://localhost:3000/api/tasks/1/complete

Send this JSON body:

{
    "completed": true
}

F. Delete a Task

Method: DELETE

http://localhost:3000/api/tasks/1

The API returns a success message and the deleted task.

8. Understand the HTTP Status Codes

Status Code Meaning
200 Request successful
201 Task created successfully
400 Invalid request data
404 Task or route not found
500 Unexpected server error

9. How Does the Application Work?

Client (Postman or Frontend)
             |
             v
      Express REST API
             |
             v
       Route Handler
             |
             v
    Validate Request Data
             |
             v
      Update Task Array
             |
             v
      Send JSON Response

The client sends a request, Express matches the route, the route handler performs the requested operation, and the server returns a JSON response.

10. Common Errors and Solutions

Problem Solution
Cannot find module 'express' Run npm install express in the project folder.
Port already in use Stop the other process or choose another available port.
req.body is undefined Ensure app.use(express.json()) appears before the routes.
Task not found Check the task ID and whether the task still exists.
POST request fails Select raw JSON in Postman and provide a valid title.
Tasks disappear after restart This project uses an in-memory array. Add a database for persistent storage.

11. Improvements for a Real Application

This project is a learning example. Before using it for real users, consider adding:

  • MongoDB or MySQL for permanent task storage.
  • User registration and secure authentication.
  • Authorization so users can access only their own tasks.
  • More complete input validation and request-size limits.
  • Rate limiting and security middleware.
  • Automated tests and production error logging.
  • Pagination when the task list becomes large.
Do not expose this in-memory example as a multi-user production task manager. It has no authentication, authorization, or persistent database.

12. Practical Assignment

Assignment: Extend the To-Do API
  1. Add a priority field: Low, Medium, or High.
  2. Add a due date to each task.
  3. Create an endpoint to display only completed tasks.
  4. Create an endpoint to display only pending tasks.
  5. Add validation for priority and due date.
  6. Test every endpoint using Postman.
  7. Record which HTTP method is used by each CRUD operation.

13. Summary

  • Express helps us build a REST API.
  • CRUD means Create, Read, Update, and Delete.
  • GET retrieves tasks, POST creates tasks, PUT updates tasks, and DELETE removes tasks.
  • PATCH can update a specific field, such as task completion status.
  • Validate request data and return appropriate HTTP status codes.
  • Use a database when data must persist after the server restarts.

Test Your Knowledge – To-Do App Project

Select the correct answer for each question.

1. Which HTTP method is used to create a new task?

2. Which Express middleware parses JSON request bodies?

3. Why do tasks disappear when this example server restarts?