Todo App Backend (Full Stack Practice Project)
ποΈ Complete Folder Structure
todo-app/
β
βββ server.js
βββ package.json
βββ .env
β
βββ config/
β βββ db.js
β
βββ models/
β βββ User.js
β βββ Todo.js
β βββ Comment.js
β
βββ controllers/
β βββ userController.js
β βββ todoController.js
β
βββ routes/
β βββ userRoutes.js
β βββ todoRoutes.js
β
βββ middleware/
β βββ errorMiddleware.js
β βββ loggerMiddleware.js
β
βββ public/
βββ index.html
βββ style.css
βββ script.js
π¦ Step 1: package.json
{
"name": "todo-app",
"version": "1.0.0",
"description": "Todo App Backend Project",
"main": "server.js",
"type": "module",
"scripts": {
"start": "node server.js",
"dev": "node --watch server.js"
},
"dependencies": {
"dotenv": "^16.4.0",
"express": "^4.19.2",
"express-validator": "^7.0.1",
"mongoose": "^8.3.2",
"morgan": "^1.10.0"
}
}
Install:
npm install
π .env
PORT=5000
MONGO_URI=mongodb://127.0.0.1:27017/todoAppDB
β config/db.js
import mongoose from "mongoose";
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI);
console.log("MongoDB Connected");
} catch (error) {
console.error("Database connection failed:", error.message);
process.exit(1);
}
};
export default connectDB;
π¦ Models
π€ models/User.js
import mongoose from "mongoose";
const userSchema = new mongoose.Schema(
{
name: {
type: String,
required: [true, "Name is required"]
},
email: {
type: String,
required: true,
unique: true
}
},
{ timestamps: true }
);
export default mongoose.model("User", userSchema);
π models/Todo.js
Uses:
β Referencing
β Validation
β Default values
import mongoose from "mongoose";
const todoSchema = new mongoose.Schema(
{
title: {
type: String,
required: [true, "Title is required"]
},
completed: {
type: Boolean,
default: false
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true
}
},
{ timestamps: true }
);
export default mongoose.model("Todo", todoSchema);
π¬ models/Comment.js (Nested Model Concept)
import mongoose from "mongoose";
const commentSchema = new mongoose.Schema(
{
text: {
type: String,
required: true
},
todo: {
type: mongoose.Schema.Types.ObjectId,
ref: "Todo"
}
},
{ timestamps: true }
);
export default mongoose.model("Comment", commentSchema);
π― Controllers
π€ controllers/userController.js
import User from "../models/User.js";
export const createUser = async (req, res, next) => {
try {
const user = await User.create(req.body);
res.status(201).json(user);
} catch (error) {
next(error);
}
};
export const getUsers = async (req, res, next) => {
try {
const users = await User.find();
res.json(users);
} catch (error) {
next(error);
}
};
π controllers/todoController.js
Includes:
β CRUD
β Filtering
β Sorting
β Pagination
β Populate
import Todo from "../models/Todo.js";
import Comment from "../models/Comment.js";
export const createTodo = async (req, res, next) => {
try {
const todo = await Todo.create(req.body);
res.status(201).json(todo);
} catch (error) {
next(error);
}
};
export const getTodos = async (req, res, next) => {
try {
const { completed, page = 1, limit = 5, sort = "createdAt" } = req.query;
let filter = {};
if (completed) {
filter.completed = completed === "true";
}
const todos = await Todo.find(filter)
.populate("user", "name email")
.sort(sort)
.limit(limit * 1)
.skip((page - 1) * limit);
res.json(todos);
} catch (error) {
next(error);
}
};
export const updateTodo = async (req, res, next) => {
try {
const todo = await Todo.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true }
);
res.json(todo);
} catch (error) {
next(error);
}
};
export const deleteTodo = async (req, res, next) => {
try {
await Todo.findByIdAndDelete(req.params.id);
res.json({ message: "Todo deleted" });
} catch (error) {
next(error);
}
};
export const addComment = async (req, res, next) => {
try {
const comment = await Comment.create({
text: req.body.text,
todo: req.params.id
});
res.status(201).json(comment);
} catch (error) {
next(error);
}
};
π Routes
routes/userRoutes.js
import express from "express";
import { createUser, getUsers } from "../controllers/userController.js";
const router = express.Router();
router.post("/", createUser);
router.get("/", getUsers);
export default router;
routes/todoRoutes.js
import express from "express";
import {
createTodo,
getTodos,
updateTodo,
deleteTodo,
addComment
} from "../controllers/todoController.js";
const router = express.Router();
router.post("/", createTodo);
router.get("/", getTodos);
router.put("/:id", updateTodo);
router.delete("/:id", deleteTodo);
router.post("/:id/comment", addComment);
export default router;
π‘ Middleware
middleware/loggerMiddleware.js
const logger = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
};
export default logger;
middleware/errorMiddleware.js
const errorHandler = (err, req, res, next) => {
res.status(500).json({
message: err.message
});
};
export default errorHandler;
π server.js
import express from "express";
import dotenv from "dotenv";
import connectDB from "./config/db.js";
import userRoutes from "./routes/userRoutes.js";
import todoRoutes from "./routes/todoRoutes.js";
import logger from "./middleware/loggerMiddleware.js";
import errorHandler from "./middleware/errorMiddleware.js";
import path from "path";
import { fileURLToPath } from "url";
dotenv.config();
connectDB();
const app = express();
app.use(express.json());
app.use(logger);
// Routes
app.use("/api/users", userRoutes);
app.use("/api/todos", todoRoutes);
// Serve Frontend
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
app.use(express.static(path.join(__dirname, "public")));
app.use(errorHandler);
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
π Frontend
public/index.html
<!DOCTYPE html>
<html>
<head>
<title>Todo App</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<h1>Todo App</h1>
<input type="text" id="title" placeholder="Enter todo">
<button onclick="createTodo()">Add Todo</button>
<ul id="todoList"></ul>
<script src="script.js"></script>
</body>
</html>
public/style.css
body {
font-family: Arial;
text-align: center;
}
li {
margin: 10px;
}
public/script.js
const API = "/api/todos";
async function fetchTodos() {
const res = await fetch(API);
const data = await res.json();
const list = document.getElementById("todoList");
list.innerHTML = "";
data.forEach(todo => {
const li = document.createElement("li");
li.innerHTML = `
${todo.title}
<button onclick="deleteTodo('${todo._id}')">Delete</button>
`;
list.appendChild(li);
});
}
async function createTodo() {
const title = document.getElementById("title").value;
await fetch(API, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title,
user: "PUT_VALID_USER_ID_HERE"
})
});
fetchTodos();
}
async function deleteTodo(id) {
await fetch(`${API}/${id}`, { method: "DELETE" });
fetchTodos();
}
fetchTodos();
π― What Concepts Students Practiced
| Concept | Used In |
| Node.js basics | server.js |
| Modules | import/export |
| Express | Routing |
| Middleware | logger + error |
| REST API | CRUD |
| MongoDB | Database |
| Mongoose | Models |
| Validation | Schema |
| Query operators | filter, sort, pagination |
| Relationships | ref + populate |
| Nested models | Comments |
| Static files | express.static |
π How To Run
npm install
npm run dev
Open:
http://localhost:5000
Now we will create:
1οΈβ£ Assignment Questions for Students (Practice Based)
2οΈβ£ Interview Questions (Fresher + Working Professional Level)
Based completely on your Todo App Backend Project (Day 1β29 concepts).
Language simple he but questions strong honge πͺ
π PART 1: Assignment Questions for Students
These assignments are divided into:
π’ Beginner Level
π‘ Intermediate Level
π΄ Advanced Level
π’ Beginner Level Assignments
1οΈβ£ Create a New Field
Add a new field priority in Todo model.
Requirements:
Type: String
Allowed values: low, medium, high
Default: medium
π Update schema properly.
2οΈβ£ Add Validation
Make title:
Minimum 5 characters
Maximum 100 characters
3οΈβ£ Create API to Get Single Todo
Create route:
GET /api/todos/:id
Return:
Todo details
Populated user data
4οΈβ£ Add Completed Filter
Modify GET todos API so that:
/api/todos?completed=true
returns only completed todos.
5οΈβ£ Add Basic Search
Add search feature:
/api/todos?search=learn
It should search in title using regex.
π‘ Intermediate Level Assignments
6οΈβ£ Pagination Improvement
Modify API to return:
{
totalTodos,
currentPage,
totalPages,
data: []
}
7οΈβ£ Add Comments Populate
Currently comments are separate.
Update:
When fetching todo,
Also populate comments related to that todo.
8οΈβ£ Create User-wise Todo API
GET /api/users/:id/todos
Return all todos of specific user.
9οΈβ£ Soft Delete Feature
Instead of deleting todo:
Add field
isDeletedHide deleted todos from API
π Add Express Validator
Add validation in routes:
Title required
User must exist
Priority allowed only specific values
π΄ Advanced Level Assignments
1οΈβ£1οΈβ£ Create Aggregation API
Create API:
GET /api/todos/stats
Return:
Total todos
Completed count
Pending count
Use MongoDB aggregation.
1οΈβ£2οΈβ£ Create Sorting by Priority
High priority first β medium β low
1οΈβ£3οΈβ£ Create Nested Comment Structure
Allow comments inside comments (replies).
Use self referencing schema.
1οΈβ£4οΈβ£ Add Rate Limiting
Use middleware to limit:
- 10 requests per minute
1οΈβ£5οΈβ£ Create Production Ready Version
Students must:
Use helmet
Use morgan
Use environment variables properly
Proper error responses
π§ PART 2: Interview Questions (Based on This Project)
Now very important π₯
These are real interview style questions.
π’ Fresher Level Questions
1. What is Node.js?
Explain how it works internally (event loop).
2. Difference between require and import?
3. What is middleware in Express?
Where did we use middleware in project?
4. What is REST API?
Explain CRUD operations in this project.
5. What is Mongoose?
Why we use Mongoose instead of native MongoDB driver?
6. What is Schema?
Difference between schema and model?
7. What is populate()?
Explain using real example from project.
8. What is async/await?
Why we use try-catch?
9. What is the difference between PUT and PATCH?
10. What is the use of dotenv?
π‘ Intermediate Interview Questions
11. How pagination works internally?
Explain skip and limit.
12. How sorting works in MongoDB?
13. What is indexing?
Where should we add index in this project?
14. How to prevent duplicate emails?
15. What happens if MongoDB connection fails?
How we handled it?
16. Difference between referencing and embedding?
When to use which?
17. What is N+1 problem in populate?
18. How to optimize large data queries?
19. What is express-validator?
Why validation is important?
20. How error handling middleware works internally?
π΄ Working Professional Level Questions
These are very strong questions.
21. How would you scale this Todo app?
22. How to handle 1 million todos?
23. How to improve performance of populate?
24. How to secure this API?
JWT
Rate limiting
Helmet
CORS
25. What is aggregation pipeline?
Explain real use case.
26. How to implement transaction in MongoDB?
27. What is lean() in Mongoose?
Why should we use it?
28. How would you deploy this project?
Environment config
Production database
PM2
29. What logging strategy would you use?
30. If project crashes randomly, how will you debug?
π― Bonus Practical Interview Round
Give students task:
π Build Todo API without looking at notes
π Add search + pagination
π Add populate
π Handle errors properly
Time limit: 1 hour
This makes you job ready.
π₯ for answer of all above question
click on this below link you will see the answers PDF .
https://t.me/knowledgefactory_22
π Stay Connected
If you found this article helpful and want to receive more such beginner-friendly and industry-relevant Node JS notes, tutorials, and project ideas β π© Subscribe to our newsletter by entering your email below.
And if you're someone who wants to prepare for tech interviews while having a little fun and entertainment, π₯ Donβt forget to subscribe to my YouTube channel β Knowledge Factory 22 β for regular content on tech concepts, career tips, and coding insights!
Stay curious. Keep building. π

