Build Your First Backend App: A To-Do List with Node.js
Ever feel overwhelmed by tasks? Juggling work, personal projects, and daily chores can be chaotic. This is where the humble to-do list comes in—a digital notepad that helps us reclaim a sense of order. But have you ever wondered how these applications are built?
In this guide, we’re not just going to use a to-do app; we’re going to build one from scratch. This is your hands-on introduction to the powerful world of backend development using Node.js. By the end of this tutorial, you'll have a fully functional web application that you can show off in your portfolio.
This project is more than just checking boxes; it's a fundamental exercise that teaches you how web servers, databases, and user interfaces interact. If you're looking to transition from basic HTML/CSS/JavaScript to becoming a full-fledged software developer, this is the perfect starting point.
To learn professional software development courses such as Python Programming, Full Stack Development, and MERN Stack, visit and enroll today at codercrafter.in.
What You'll Be Building
We will create a simple but powerful To-Do List application with the following features:
View Tasks: See a list of all your current tasks.
Add Tasks: Input new tasks through a simple form.
Mark as Complete: Check off tasks to mark them as done (with a visual strikethrough).
Delete Tasks: Remove tasks you no longer need.
Tech Stack We'll Use:
Backend: Node.js with the Express.js framework
Template Engine: EJS (Embedded JavaScript) to generate HTML on the server
Database: MongoDB with Mongoose ODM (Object Data Modeling) to store our tasks
Styling: A bit of CSS to make it look presentable
Prerequisites
Before we dive in, make sure you have the following installed on your machine:
Node.js and npm: Download and install from nodejs.org. npm (Node Package Manager) comes bundled with it.
A Code Editor: VS Code is a fantastic and free choice.
MongoDB Atlas Account: We'll use a cloud-based MongoDB database, which is free and easy to set up. Head over to MongoDB Atlas to create an account.
Step 1: Setting Up the Project
First, let's create a new directory for our project and initialize it.
Open your terminal and run:
bash
mkdir node-todo-list
cd node-todo-list
npm init -y
The npm init -y command creates a package.json file with default values. This file will keep track of all our project's dependencies (the external packages we need).
Step 2: Installing Dependencies
Now, let's install the necessary packages:
bash
npm install express mongoose ejs
express: A fast, unopinionated, and minimalist web framework for Node.js. It handles routing and server logic.
mongoose: An ODM library for MongoDB and Node.js. It provides a straight-forward, schema-based solution to model our application data.
ejs: Our template engine. It lets us generate HTML with embedded JavaScript, allowing us to dynamically inject data from our server into the web pages.
We'll also install nodemon as a development dependency. This tool automatically restarts our server whenever we make changes, which is a huge time-saver.
bash
npm install --save-dev nodemon
Now, open your package.json file and add a start script to make running the app easier.
json
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
},
Step 3: The Server Foundation (index.js)
Create a file named index.js in your project's root directory. This will be the entry point of our application.
javascript
const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const app = express();
const port = 3000;
// Middleware
app.use(express.urlencoded({ extended: true })); // To parse form data
app.use(express.static('public')); // To serve static files (CSS, JS)
app.set('view engine', 'ejs'); // Set EJS as the template engine
// Connect to MongoDB (We'll set this up next)
mongoose.connect('your-mongodb-connection-string-here', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// Define a simple route
app.get('/', (req, res) => {
res.render('index', { message: 'Hello from the server!' });
});
// Start the server
app.listen(port, () => {
console.log(`Server is running on http://localhost:${port}`);
});
This code sets up a basic Express server, configures EJS, and defines a single route for the home page (/). When you visit the home page, it will render an index.ejs file and pass a message variable to it.
Step 4: Setting Up the Database with Mongoose
A. Get Your MongoDB Connection String:
Log in to your MongoDB Atlas account.
Create a new cluster (the free tier is fine).
Go to "Database Access" and create a user with a password.
Go to "Network Access" and add your current IP address (or 0.0.0.0/0 to allow access from anywhere, though this is less secure for production).
Click "Connect" on your cluster, choose "Connect your application," and copy the connection string. It will look like this: mongodb+srv://:@cluster0.xxxxx.mongodb.net/myFirstDatabase?retryWrites=true&w=majority.
B. Create a Task Model:
Create a folder named models and inside it, a file named Task.js. This file will define the structure of our tasks.
javascript
// models/Task.js
const mongoose = require('mongoose');
const taskSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
completed: {
type: Boolean,
default: false, // A new task is not completed by default
},
}, { timestamps: true }); // This adds 'createdAt' and 'updatedAt' fields
module.exports = mongoose.model('Task', taskSchema);
C. Update index.js with the Connection and Routes:
Now, let's update our index.js to connect to the database and add the full CRUD (Create, Read, Update, Delete) logic.
javascript
const express = require('express');
const mongoose = require('mongoose');
const Task = require('./models/Task'); // Import the Task model
const app = express();
const port = 3000;
// Middleware
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));
app.set('view engine', 'ejs');
// Connect to MongoDB (Replace the placeholder with your actual string)
mongoose.connect('mongodb+srv://your-username:your-password@cluster0.xxxxx.mongodb.net/todoapp?retryWrites=true&w=majority')
.then(() => console.log('Connected to MongoDB!'))
.catch(err => console.error('Could not connect to MongoDB...', err));
// READ - Display all tasks
app.get('/', async (req, res) => {
try {
const tasks = await Task.find(); // Get all tasks from the database
res.render('index', { tasks: tasks });
} catch (err) {
res.status(500).send(err.message);
}
});
// CREATE - Add a new task
app.post('/add', async (req, res) => {
const newTask = new Task({
title: req.body.title
});
try {
await newTask.save();
res.redirect('/'); // Redirect back to the home page
} catch (err) {
res.status(500).send(err.message);
}
});
// UPDATE - Toggle task completion status
app.post('/toggle/:id', async (req, res) => {
try {
const task = await Task.findById(req.params.id);
task.completed = !task.completed; // Flip the boolean value
await task.save();
res.redirect('/');
} catch (err) {
res.status(500).send(err.message);
}
});
// DELETE - Remove a task
app.post('/delete/:id', async (req, res) => {
try {
await Task.findByIdAndDelete(req.params.id);
res.redirect('/');
} catch (err) {
res.status(500).send(err.message);
}
});
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
Step 5: Building the Frontend (Views)
Create a folder named views. Inside it, create a file named index.ejs. This is our main template.
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Node.js Todo App</title>
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div class="container">
<h1>My Node.js Todo List</h1>
<form action="/add" method="POST" class="add-form">
<input type="text" name="title" placeholder="Add a new task..." required>
<button type="submit">Add</button>
</form>
<ul class="task-list">
<% tasks.forEach(function(task) { %>
<li class="<%= task.completed ? 'completed' : '' %>">
<form action="/toggle/<%= task._id %>" method="POST" style="display: inline;">
<input type="checkbox" <%= task.completed ? 'checked' : '' %> onChange="this.form.submit()">
<span><%= task.title %></span>
</form>
<form action="/delete/<%= task._id %>" method="POST" style="display: inline;">
<button type="submit" class="delete-btn">×</button>
</form>
</li>
<% }); %>
</ul>
</div>
</body>
</html>
Notice the EJS syntax (<% %> and <%= %>). It lets us loop through the tasks array we passed from the server and conditionally apply styles and checks.
Step 6: Adding Basic Styling
Create a folder named public and inside it, a file named styles.css.
css
body {
font-family: 'Arial', sans-serif;
background-color: #f4f4f4;
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
text-align: center;
color: #333;
}
.add-form {
display: flex;
margin-bottom: 20px;
}
.add-form input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
}
.add-form button {
padding: 10px 20px;
background: #5cb85c;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.task-list {
list-style: none;
padding: 0;
}
.task-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.task-list li.completed span {
text-decoration: line-through;
color: #888;
}
.delete-btn {
background: #d9534f;
color: white;
border: none;
border-radius: 50%;
width: 25px;
height: 25px;
cursor: pointer;
font-size: 16px;
}
Step 7: Running the Application
Back in your terminal, run:
bash
npm run dev
Open your browser and go to , You should see your fully functional To-Do List app! Try adding, completing, and deleting tasks.
Best Practices & Next Steps
Congratulations on building your app! This is a fantastic start. However, a production-ready app would involve a few more considerations:
Environment Variables: Never hardcode sensitive data like your MongoDB password. Use the dotenv package to store them in a .env file.
Input Validation: Always validate and sanitize user input on the server-side to prevent malicious data.
Error Handling: Implement more robust error handling and show user-friendly messages.
Frontend JavaScript: Use AJAX/Fetch API to update tasks without reloading the entire page, providing a smoother user experience.
Authentication: Add user accounts so everyone can have their own private to-do list.
Deployment: Deploy your app to a platform like Heroku, Render, or Railway.
Building this app gives you a tangible understanding of the MERN stack's "MEN" part (MongoDB, Express, Node.js). To master the "R" (React) and become a job-ready Full Stack Developer, you need a structured learning path.
This simple To-Do app is just the beginning. To learn professional software development courses that cover advanced concepts, industry best practices, and build complex, real-world projects, visit and enroll today at codercrafter.in. Our Full Stack Development and MERN Stack programs are designed to take you from beginner to professional.
Frequently Asked Questions (FAQs)
Q1: Why use MongoDB for a simple to-do list?
MongoDB is a NoSQL database that is very flexible and easy to start with. Its document-based structure maps well to JavaScript objects, making it a natural fit for Node.js applications.
Q2: Can I use a different database like MySQL?
Absolutely! You would use an ORM like Sequelize instead of Mongoose. The core concepts of routes and controllers would remain very similar.
Q3: What is the purpose of the public folder?
The public folder is for static assets that the client's browser needs to download, like CSS files, client-side JavaScript, images, and fonts. The express.static middleware makes these files accessible.
Q4: Is this app ready for production?
Not quite. For production, you'd need to set environment variables, improve error handling, and deploy it to a cloud server. It's a great foundation, though!
Conclusion
You've just built a complete web application! You've successfully set up a Node.js server, connected it to a real database, defined data models, created routes to handle user actions, and built a dynamic frontend. These are the core concepts that power nearly every web application on the internet today.
The journey of a thousand miles begins with a single step, and you've just taken a massive one. Keep experimenting, add new features, and most importantly, keep building.
Top comments (0)