Skip to main content

Command Palette

Search for a command to run...

Todo App Backend (Full Stack Practice Project)

Published
β€’9 min readβ€’View as Markdown
P
Hi there, tech enthusiasts! I'm a passionate Software Developer driven by a love for continuous learning and innovation. I thrive on exploring new tools and technologies, pushing boundaries, and finding creative solutions to complex problems. What You'll Find Here On my Hashnode blog, I share: πŸš€ In-depth explorations of emerging technologies πŸ’‘ Practical tutorials and how-to guides πŸ”§Insights on software development best practices πŸš€Reviews of the latest tools and frameworks πŸ’‘ Personal experiences from real-world projects. Join me as we bridge imagination and implementation in the tech world. Whether you're a seasoned pro or just starting out, there's always something new to discover! Let’s connect and grow together! 🌟

πŸ—‚οΈ 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

ConceptUsed In
Node.js basicsserver.js
Modulesimport/export
ExpressRouting
Middlewarelogger + error
REST APICRUD
MongoDBDatabase
MongooseModels
ValidationSchema
Query operatorsfilter, sort, pagination
Relationshipsref + populate
Nested modelsComments
Static filesexpress.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.


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 isDeleted

  • Hide 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. πŸš€

More from this blog

T

Technologies: Tools & Tips | Expert Guides on Latest Tech Tools

126 posts