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.
A To-Do application helps users manage tasks such as studying, completing assignments, attending classes, and finishing projects.
Our application will support these features:
| 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 |
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.
todo-project/
│
├── node_modules/
├── package.json
├── package-lock.json
└── server.js
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}`);
});
node server.js
Open this URL in your browser:
http://localhost:3000/
Expected output:
Welcome to the To-Do App API!
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;
Replace the contents of server.js with the following
complete code.
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}`);
});
Start the server, open Postman, and send HTTP requests to the following endpoints.
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.
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"
}
}
Method: GET
http://localhost:3000/api/tasks/1
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
}
Method: PATCH
http://localhost:3000/api/tasks/1/complete
Send this JSON body:
{
"completed": true
}
Method: DELETE
http://localhost:3000/api/tasks/1
The API returns a success message and the deleted task.
| Status Code | Meaning |
|---|---|
| 200 | Request successful |
| 201 | Task created successfully |
| 400 | Invalid request data |
| 404 | Task or route not found |
| 500 | Unexpected server error |
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.
| 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. |
This project is a learning example. Before using it for real users, consider adding:
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?