Building a Todo App using Express.js + MongoDB

I'm Shubham (@shubhamsinghbundela), I'm a Software Engineer, a Full-stack developer, a tech enthusiast, and a technical writer here on @Hashnode. I have a strong zeal to share my acquired knowledge and I am also willing to learn from others.
Last time, I wrote about Understanding HTTP Requests, Responses, and Methods using Node.js.
This time, I took it one step further…
I built a Todo Application using Express.js and MongoDB
What I Built
User Signup & Signin
Create Todo
Get Todos of logged-in user
Protected routes using middleware
MongoDB for database
Step 1: Setup Express Server
First, I created a basic Express server:
const express = require("express");
const app = express();
app.use(express.json());
app.listen(3000, () => {
console.log("Server is running on port 3000");
});
Step 2: Setup MongoDB
Create account & login
Create a Cluster
Go to Database Access
- Create username & password
Go to Network Access
- Add IP:
0.0.0.0/0(for development)
- Add IP:
Click Connect
Copy connection string
Example:
mongodb+srv://username:password@cluster.mongodb.net/dbname
MongoDB Connection Code
Before connecting MongoDB, first we need to install mongoose.
Step 1: Install Mongoose
Run this command in your project:
npm install mongoose
Step 2: Connect to MongoDB
Now write the connection code:
const mongoose = require("mongoose");
async function connectDB() {
try {
await mongoose.connect("mongodb+srv://<username>:<password>@todo.dtrpx.mongodb.net/todo");
console.log("MongoDB connected");
} catch (err) {
console.error("Connection error:", err);
}
}
connectDB();
Initially, I tried connecting to MongoDB (cloud) but faced a DNS issue
Problem:
MongoDB connection was failing due to DNS resolution.
Fix:
After watching a YouTube video: , I added this:
const dns = require("dns");
dns.setServers(["1.1.1.1", "8.8.8.8"]);
This fixed the issue and I was able to connect to cloud MongoDB.
Step 3: Create Schema using Mongoose
Before writing any backend or frontend logic, I first designed how my data should look.
This step is very important.
Learning:
Always define your schema first before building APIs or UI.
Because:
It gives clarity on what data you need
Helps structure your database properly
Makes backend logic easier to write
const userSchema = new mongoose.Schema({
userName: String,
password: String,
firstName: String,
lastName: String
})
const todoSchema = new mongoose.Schema({
description: String,
userId: mongoose.Types.ObjectId
})
const userModel = mongoose.model("users", userSchema);
const todoModel = mongoose.model("todos", todoSchema);
module.exports = {
userModel: userModel,
todoModel: todoModel
}
Step 4: Signup Route
Before writing code, let’s understand the goal.
What we want to achieve:
Allow a new user to register
Store user details in database
Prevent duplicate users
app.post("/signup", async (req, res) => {
const { userName, password, firstName, lastName } = req.body;
// checking user exist or not
const existingUser = await userModel.findOne({
userName,
password,
});
if (existingUser) {
return res.status(403).json({
message: "User already exists",
});
}
// added new user in userModel collection
const newUser = await userModel.create({
userName,
password,
firstName,
lastName,
});
res.json({
id: newUser._id,
});
});
Step 5: Signin + JWT Token
Before writing code, let’s understand the goal.
What we want to achieve:
Verify user credentials
If valid → allow login
Generate a JWT token for authentication
app.post("/signin", async (req, res) => {
const { userName, password } = req.body;
const userExist = await userModel.findOne({ userName });
if (!userExist) {
return res.status(404).json({
message: "User not found",
});
}
const token = jwt.sign(
{ userId: userExist.id },
"shubham123"
);
res.json({ token });
});
Step 6: Auth Middleware
This ensures only logged-in users can access routes.
function authMiddleware(req, res, next) {
const token = req.headers.token;
const decode = jwt.verify(token, "shubham123");
req.userId = decode.userId;
next();
}
Step 7: Create Todo
Before writing the code, let’s understand
What are we trying to achieve?
We want:
1. Only logged-in users should be able to create a todo i.e. Each todo should be linked to the specific user who created it
When user tries to create a todo, first authentication happens using authMiddleware.
It verifies the token
Extracts
userIdAttaches it to
req.userId
Then we use this userId to link the todo with the logged-in user.
So each todo belongs to a specific user, and later we can fetch user-specific todos.
app.post("/todo", authMiddleware, async (req, res) => {
const newTodo = await todoModel.create({
description: req.body.description,
userId: req.userId
});
res.json({
id: newTodo._id
});
});
Step 8: Get All Todos
app.get("/todos", authMiddleware, async (req, res) => {
const allTodos = await todoModel.find({
userId: req.userId
});
res.json({ allTodos });
});
Testing using Postman
I used Postman to test APIs.
Signup
Method:
POSTBody (JSON):
{
"userName": "shubham",
"password": "1234",
"firstName": "Shubham",
"lastName": "Singh"
}
Signin
Method:
POSTURL:
/signin
Copy the token from response
Create Todo
Method:
POSTURL:
/todoHeaders:
token: YOUR_TOKEN
- Body:
{
"description": "Learn Express"
}
Get Todos
Method:
GETURL:
/todosHeaders:
token: YOUR_TOKEN
Complete Source Code
👉 I’ve uploaded the complete project on GitHub. You can check it here:
GitHub Repo:
https://github.com/shubhamsinghbundela/Todo-application-using-express/tree/main




