Skip to main content

Command Palette

Search for a command to run...

Refactoring a Trello Backend into a Scalable REST API Architecture

Updated
19 min read
Refactoring a Trello Backend into a Scalable REST API Architecture
S

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.

Till now, the backend was working, but everything was in a single file / unstructured format.

Now the goal is to:

  • Follow clean folder structure

  • Make code scalable & maintainable


Before diving into this structured version, I recommend reading my initial implementation where everything was built in a single file: Building a Trello - Backend from Scratchhttps://shubhamsinghbundela.hashnode.dev/building-a-trello-backend-from-scratch


Step 1: Initialize Project

npm init -y

It Creates package.json


Step 2: Install Dependencies

npm install express
npm install --save-dev nodemon
npm install dotenv // This package is used to load environment variables from the .env file.

Step 3: Setup App Entry (src/app.js)

Create src/app.js

import express from "express";

const app = express();

app.use(express.json());

export default app;

This file is responsible for:

  • Initializing express app

  • Adding global middlewares


Step 4: Environment Variables

Create .env file:

PORT=3000
NODE_ENV=development
MONGODB_URI=your_mongodb_connection_string

Step 5: Server Entry Point (server.js)

create server.js

import "dotenv/config"; //This automatically loads .env variables into process.env
import app from "./src/app.js";

const PORT = process.env.PORT || 3000;

const start = async () => {
    app.listen(PORT, () => {
        console.log(`Server is running at \({PORT} in \){process.env.NODE_ENV} mode`);
    });
};

start().catch((err) => {
    console.error("Failed to start server", err);
    process.exit(1);
});

This file is responsible for:

  • Starting the server

  • Handling startup errors


Step 6: Scripts Setup

Update package.json:

"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}

Use:

npm run dev

Step 7: Setup Database Connection

Create:

src/common/config/db.js
import mongoose from "mongoose";
import dns from "dns";

dns.setServers(["1.1.1.1", "8.8.8.8"]);

const connectDB = async () => {
    const conn = await mongoose.connect(process.env.MONGODB_URI);

    console.log(`MongoDB connected: ${conn.connection.host}`);
};

export default 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 8: Standard API Response Structure

Create:

src/common/utils/api-response.js
class ApiResponse {
    static ok(res, message, data = null) {
        return res.status(200).json({
            success: true,
            message,
            data
        });
    }
}

export default ApiResponse;

Why This is Important

Instead of writing:

res.status(200).json({ message: "Success" });

To avoid repeating response logic across multiple APIs, I implemented a reusable utility using the DRY (Don't Repeat Yourself) principle:

ApiResponse.ok(res, "Success", data);

Step 9: Standard API Error Structure

While building APIs, handling errors properly is as important as handling success responses.

1**. Create Custom Error Class**

Create:

src/common/utils/api-error.js
class ApiError extends Error {
    // Inherit from built-in JavaScript Error
    constructor(statusCode, message) {
        super(message);

        this.statusCode = statusCode;

        // Capture clean stack trace
        Error.captureStackTrace(this, this.constructor);
    }

    static badRequest(message = "Bad Request") {
        return new ApiError(400, message);
    }

    static forbidden(message = "Forbidden") {
        return new ApiError(403, message);
    }

    static notFound(message = "Not Found") {
        return new ApiError(404, message);
    }
}

export default ApiError;

Why Extend Error?

JavaScript provides built-in errors like:

  • Error

  • TypeError

  • ReferenceError

But they don’t include HTTP status codes, which are required in APIs.

2. Global Error Middleware

Create:

src/common/middleware/error.middleware.js
import ApiError from "../utils/api-error.js";

const errorHandler = (err, req, res, next) => {

    // Handle known (custom) errors
    if (err instanceof ApiError) {
        return res.status(err.statusCode).json({
            success: false,
            message: err.message
        });
    }

    // Handle unknown errors
    return res.status(500).json({
        success: false,
        message: "Internal Server Error"
    });
};

export default errorHandler;

3. Register Middleware in App

Update src/app.js:

import express from "express";
import authRoute from "./modules/auth/auth.routes.js";
import errorHandler from "./common/middleware/error.middleware.js";

const app = express();
app.use(express.json());

app.use(errorHandler);

export default app;

This must be the last middleware in the app.

Final Error Flow

Request → Route → Controller → Service → throws ApiError → Passes to errorHandler middleware → JSON response 

Step 10: Authentication Module (Modular Structure)

Now that we have:

  • Clean folder structure

  • Database setup

  • Standard response & error handling

Next step is to build a modular authentication system


Module Structure

Inside src/modules/auth/, I created:

auth/
├── auth.controller.js
├── auth.middleware.js
├── auth.model.js
├── auth.routes.js
├── auth.service.js

Why This Structure?

Each layer has a clear responsibility:

  • Model → Database schema

  • Routes → Define API endpoints

  • Controller → Handle request/response

  • Service → Business logic

  • Middleware → Authentication / validation

This makes code clean, maintainable, and scalable


Create User Schema (auth.model.js)

import mongoose from "mongoose";

const userSchema = new mongoose.Schema({
    username: String,
    password: String
}, { timestamps: true });

const userModel = mongoose.model("users", userSchema);

export default userModel;

timestamps automatically adds:

  • createdAt

  • updatedAt


Register Auth Routes in App.js

Update src/app.js:

import express from "express";
import authRoute from "./modules/auth/auth.routes.js";
import errorMiddleware from "./common/middleware/error.middleware.js";

const app = express();

app.use(express.json());

// Register routes
app.use("/api/auth", authRoute);

// Global error handler
app.use(errorMiddleware);

export default app;

All auth APIs will now be available under:

/api/auth/*

Define Routes (auth.routes.js)

import { Router } from "express";
import * as controller from "./auth.controller.js";

const router = Router();

router.post("/signup", controller.signup);

export default router;

Controller Layer (auth.controller.js)

import ApiResponse from "../../common/utils/api-response.js";
import * as authService from "./auth.service.js";

const signup = async (req, res, next) => {
  try {
    const user = await authService.signup(req.body);

    ApiResponse.ok(res, "User get Created", user);
  } catch (error) {
    next(error); // pass error to global error middleware
  }
};

export { signup };

Responsibility of Controller

  • Calls service layer

  • Sends response using ApiResponse

  • Keeps logic minimal


Business Logic (auth.service.js)

import userModel from "./auth.model.js";
import ApiError from "../../common/utils/api-error.js";

const signup = async ({ username, password }) => {

    // Step 1: Check if user already exists
    const userExists = await userModel.findOne({ username });

    if (userExists) {
        throw ApiError.forbidden("User Already Exists");
    }

    // Step 2: Create new user
    const newUser = await userModel.create({
        username,
        password
    });

    return newUser;
};

export {
    signup
};

Final Result: Testing /api/auth/signup API Using Postman


Request Flow

POST /api/auth/signup
  ↓
auth.routes.js
  ↓
auth.controller.js
  ↓
auth.service.js
  ↓
MongoDB

Step 11: Implement Signin

Now that the signup API is ready, the next step is to allow users to login (signin) and generate a token for authentication.


Define Routes

Inside auth.route.js, we define the signin route:

import { Router } from "express";
import * as controller from "./auth.controller.js";

const router = Router();

router.post("/signup", controller.signup);
router.post("/signin", controller.signin);

export default router;

This creates an endpoint:

POST /api/auth/signin

Controller Layer

Now we handle the request in the controller.

const signin = async (req, res, next) => {
  try {
    const token = await authService.signin(req.body);

    ApiResponse.ok(res, "Signin successfully", token);
  } catch (error) {
    next(error);
  }
};

Responsibility of Controller:

  • Receive request data (req.body)

  • Call service layer

  • Send response using ApiResponse

  • Pass errors to global error handler


Service Layer (Business Logic)

Now the actual authentication logic lives in auth.service.js:

const signin = async ({ username, password }) => {
  const userExists = await userModel.findOne({
    username: username,
    password: password,
  });

  if (!userExists) {
    throw ApiError.notFound("User not found");
  }

  const token = jwt.sign(
    {
      userId: userExists.id,
    },
    "shubham123"
  );

  return token;
};

Final Result: Testing /api/auth/signin API Using Postman


Step 12: Create organisation inside Organisation Module

Now that authentication is complete, the next step is to allow users to create organisations — similar to how Trello works.


Requirements

After login, a user should be able to:

  • Create an organisation

  • Automatically become the admin

  • Ensure organisation names are unique

  • Only authenticated users can create organisations


Module Structure

Inside src/modules/, create a new folder:

src/modules/org/
├── org.controller.js
├── org.middleware.js
├── org.model.js
├── org.routes.js
├── org.service.js

Create Organisation Schema

Create:

src/modules/org/org.model.js
import mongoose from "mongoose";

const orgSchema = new mongoose.Schema({
    orgName: "String",
    description: "String",
    admin: mongoose.Types.ObjectId,
    member: [mongoose.Types.ObjectId]
}, { timestamps: true });

const orgModel = mongoose.model("organisations", orgSchema);

export default orgModel;

Authentication Middleware

To ensure only logged-in users can create organisations:

Inside:

src/modules/auth/auth.middleware.js
import jwt from "jsonwebtoken";

const authMiddleware = (req, res, next) => {
    const token = req.headers.token;

    const decode = jwt.verify(token, "shubham123");

    req.userId = decode.userId;

    next();
};

export default authMiddleware;

Register Organisation Routes

Update src/app.js:

import orgRoute from "./modules/org/org.routes.js";

app.use("/api/org", orgRoute);

Now all organisation APIs will be available under:

/api/org/* 

Define Routes

Create:

src/modules/org/org.routes.js
import { Router } from "express";
import * as controller from "./org.controller.js";
import authMiddleware from "../auth/auth.middleware.js";

const router = Router();

router.post(
  "/create-organisation",
  authMiddleware,
  controller.createOrganisation
);

export default router;

Controller Layer

Create:

src/modules/org/org.controller.js
import ApiResponse from "../../common/utils/api-response.js";
import * as orgService from "./org.service.js";

const createOrganisation = async (req, res, next) => {
  try {
    const org = await orgService.createOrganisation(req);

    ApiResponse.ok(
      res,
      "Org get created",
      org
    );

  } catch (error) {
    next(error);
  }
};

export { createOrganisation };

Responsibility of Controller

  • Calls service layer

  • Sends success response

  • Passes errors to global middleware


Business Logic (Service Layer)

Create:

src/modules/org/org.service.js
import ApiError from "../../common/utils/api-error.js";
import orgModel from "./org.model.js";

const createOrganisation = async (req) => {

    // Step 1: Check if organisation already exists
    const orgExist = await orgModel.findOne({
        orgName: req.body.orgName
    });

    if (orgExist) {
        throw ApiError.forbidden("Organisation already exists");
    }

    // Step 2: Create organisation
    const newOrg = await orgModel.create({
        orgName: req.body.orgName,
        description: req.body.description,
        admin: req.userId,
        member: []
    });

    return newOrg;
};

export { createOrganisation };

Request Flow

POST /api/org/create-organisation
   ↓
auth.middleware.js (verify token)
   ↓
org.controller.js
   ↓
org.service.js
   ↓
MongoDB
   ↓
Response

Final Result: Testing api/org/create-organisation API Using Postman


Step 13: Add Members to Organisation

Now that organisations are created, the next step is to allow admins to add members — similar to how teams work in Trello.


Requirement

  • Admin can add users to their organisation

  • Users are added using their username

  • Only existing users can be added

  • Prevent adding the same user twice


Update Routes

Update:

src/modules/org/org.routes.js
import { Router } from "express";
import * as controller from "./org.controller.js";
import authMiddleware from "../auth/auth.middleware.js";

const router = Router();

router.post("/create-organisation", authMiddleware, controller.createOrganisation);

router.post("/add-member", authMiddleware, controller.addMember);

export default router;

Controller Layer

Update:

src/modules/org/org.controller.js
const addMember = async (req, res, next) => {
  try {
    const data = await orgService.addMember(req);

    ApiResponse.ok(
      res,
      "Member get added",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  addMember
};

Responsibility

  • Calls service layer

  • Sends response

  • Passes errors to middleware


Business Logic (Service Layer)

Update:

src/modules/org/org.service.js
import ApiError from "../../common/utils/api-error.js";
import orgModel from "./org.model.js";
import userModel from "../auth/auth.model.js";

const addMember = async (req) => {

    const newMember = req.body.member;

    // Step 1: Check user exists
    const newMemberUser = await userModel.findOne({
        username: newMember
    });

    if (!newMemberUser) {
        throw ApiError.notFound("User not exist");
    }

    // Step 2: Find organisation (admin only)
    const orgDetails = await orgModel.findOne({
        admin: req.userId
    });

    if (!orgDetails) {
        throw ApiError.notFound("Organisation not exists");
    }

    // Step 3: Prevent duplicate members
    const memberExists = orgDetails.member.includes(newMemberUser._id);

    if (memberExists) {
        throw ApiError.badRequest("Member already exists in organisation");
    }

    // Step 4: Add member
    orgDetails.member.push(newMemberUser._id);

    await orgDetails.save();

    return orgDetails;
};

export {
    addMember
};

Request Flow

POST /api/org/add-member
  ↓
auth.middleware.js (verify token)
  ↓
org.controller.js
  ↓
org.service.js
  ↓
MongoDB
  ↓
Response

Final Result: Testing api/org/add-member API Using Postman


Step 14: Remove Member from Organisation

After adding members, the next important feature is allowing admins to remove members — similar to how team management works in Trello.


Requirement

Admins should be able to:

  • Remove users from the organisation

  • Only remove existing members

  • Only admin can perform this action


Update Routes

Update:

src/modules/org/org.routes.js
import { Router } from "express";
import * as controller from "./org.controller.js";
import authMiddleware from "../auth/auth.middleware.js";

const router = Router();

router.post("/create-organisation", authMiddleware, controller.createOrganisation);

router.post("/add-member", authMiddleware, controller.addMember);

router.delete("/delete-member", authMiddleware, controller.deleteMember);

export default router;

Controller Layer

Update:

src/modules/org/org.controller.js
const deleteMember = async (req, res, next) => {
  try {
    const data = await orgService.deleteMember(req);

    ApiResponse.ok(
      res,
      "Member get deleted",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  deleteMember
};

Business Logic (Service Layer)

Update:

src/modules/org/org.service.js
import ApiError from "../../common/utils/api-error.js";
import orgModel from "./org.model.js";
import userModel from "../auth/auth.model.js";

const deleteMember = async (req) => {

    const deleteUser = req.body.username;

    // Step 1: Check user exists
    const deleteUserData = await userModel.findOne({
        username: deleteUser
    });

    if (!deleteUserData) {
        throw ApiError.notFound("User not exist");
    }

    // Step 2: Find organisation (admin only)
    const orgDetails = await orgModel.findOne({
        admin: req.userId
    });

    if (!orgDetails) {
        throw ApiError.notFound("Organisation not exists");
    }

    // Step 3: Check if user is actually a member
    const isMember = orgDetails.member.some(
        id => id.toString() === deleteUserData._id.toString()
    );

    if (!isMember) {
        throw ApiError.badRequest("User is not a member of this organisation");
    }

    // Step 4: Remove member
    orgDetails.member = orgDetails.member.filter(
        id => id.toString() !== deleteUserData._id.toString()
    );

    await orgDetails.save();

    return orgDetails;
};

export {
    deleteMember
};

Request Flow

DELETE /api/org/delete-member
   ↓
auth.middleware.js (verify token)
  ↓
org.controller.js
  ↓
org.service.js
  ↓
MongoDB
  ↓
Response

Final Result: Testing api/org/delete-member API Using Postman


Step 15: Create Board

Now comes the most important feature — boards — just like in Trello.


Requirement

  • Each organisation can have multiple boards

  • Only authorized users (admin) can create boards

  • Each board belongs to a specific organisation


Module Structure

Inside src/modules/, create:

board/
├── board.controller.js
├── board.middleware.js
├── board.model.js
├── board.routes.js
├── board.service.js

Create Board Schema

Create:

src/modules/board/board.model.js
import mongoose from "mongoose";

const boardSchema = mongoose.Schema({
    boardName: "String",
    organisationId: mongoose.Types.ObjectId
});

const boardModel = mongoose.model("boards", boardSchema);

export default boardModel;

Define Routes

Create:

src/modules/board/board.routes.js
import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from "./board.controller.js";

const router = Router();

router.post("/create-board", authMiddleware, controller.createBoard);

export default router;

Register Route in App

Update src/app.js:

import boardRoute from "./modules/board/board.routes.js";

app.use("/api/board", boardRoute);

Controller Layer

Create:

src/modules/board/board.controller.js
import ApiResponse from "../../common/utils/api-response.js";
import * as boardService from "./board.service.js";

const createBoard = async (req, res, next) => {
  try {
    const data = await boardService.createBoard(req);

    ApiResponse.ok(
      res,
      "Board get created",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  createBoard
};

Business Logic (Service Layer)

Create:

src/modules/board/board.service.js
import ApiError from "../../common/utils/api-error.js";
import orgModel from "../org/org.model.js";
import boardModel from "./board.model.js";

const createBoard = async (req) => {

    const boardName = req.body.boardName;

    // Step 1: Check organisation (admin only)
    const orgDetails = await orgModel.findOne({
        admin: req.userId
    });

    if (!orgDetails) {
        throw ApiError.notFound("Organisation not exists");
    }

    // Step 2: Create board
    const newBoard = await boardModel.create({
        boardName,
        organisationId: orgDetails._id
    });

    return newBoard;
};

export {
    createBoard
};

Request Flow

POST /api/board/create-board
  ↓
auth.middleware.js (verify token)
  ↓
board.controller.js
  ↓
board.service.js
  ↓
MongoDB
  ↓
Response

Final Result: Testing api/board/create-board API Using Postman


Step 16: Task Management (Create Task)

Now comes the most important part of any Trello-like application — Tasks.

This is where users actually track their work.


Requirement

We want users to:

  • Create tasks inside a board

  • Update task status (Todo → In Progress → Done) (next step)

  • Delete tasks (next step)

  • Ensure tasks are tied to the logged-in user

This forms the core task lifecycle system


Module Structure

Inside src/modules/, create:

task/
├── task.controller.js
├── task.middleware.js
├── task.model.js
├── task.routes.js
├── task.service.js

Task Schema

Create:

src/modules/task/task.model.js
import mongoose from "mongoose";

const taskSchema = mongoose.Schema({
    description: "String",
    status: "String",
    boardId: mongoose.Types.ObjectId,
    userId: mongoose.Types.ObjectId,
});

const taskModel = mongoose.model("tasks", taskSchema);

export default taskModel;

Define Routes

Create:

src/modules/task/task.routes.js
import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from "./task.controller.js";

const router = Router();

router.post("/create-task", authMiddleware, controller.createTask);

export default router;

Register Route in App

Update src/app.js:

import taskRouter from "./modules/task/task.routes.js";

app.use("/api/task", taskRouter);

Controller Layer

Create:

src/modules/task/task.controller.js
import ApiResponse from "../../common/utils/api-response.js";
import * as taskService from "./task.service.js";

const createTask = async (req, res, next) => {
  try {
    const data = await taskService.createTask(req);

    ApiResponse.ok(
      res,
      "Task created successfully",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  createTask
};

Business Logic (Service Layer)

Create:

src/modules/task/task.service.js
import ApiError from "../../common/utils/api-error.js";
import boardModel from "../board/board.model.js";
import taskModel from "./task.model.js";

const createTask = async (req) => {

    const { description, status, boardId } = req.body;

    // Step 1: Validate input
    if (!description || !status || !boardId) {
        throw ApiError.badRequest("All fields are required");
    }

    // Step 2: Validate status
    const allowedStatus = ["Todo", "In Progress", "Done"];

    if (!allowedStatus.includes(status)) {
        throw ApiError.badRequest("Invalid status");
    }

    // Step 3: Check board exists
    const board = await boardModel.findById(boardId);

    if (!board) {
        throw ApiError.notFound("Board not found");
    }

    // Step 4: Prevent duplicate task in same board
    const taskExists = await taskModel.findOne({
        userId: req.userId,
        description,
        boardId
    });

    if (taskExists) {
        throw ApiError.forbidden("Task already exists in this board");
    }

    // Step 5: Create task
    const newTask = await taskModel.create({
        userId: req.userId,
        description,
        status,
        boardId
    });

    return newTask;
};

export {
    createTask
};

Request Flow

POST /api/task/create-task
        ↓
auth.middleware.js (verify token)
        ↓
task.controller.js
        ↓
task.service.js
        ↓
MongoDB
        ↓
Response

Final Result: Testing api/task/create-task API Using Postman


Step 17: Update Task Status

Now that tasks can be created, the next step is to update their status — this is what enables the classic workflow of moving tasks across stages (Todo → In Progress → Done), just like in Trello.


Requirement

When updating a task:

  • Identify the task using taskId

  • Validate the new status

  • Ensure the task belongs to the logged-in user

  • Update the status


Define Route

Update:

src/modules/task/task.routes.js
import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from "./task.controller.js";

const router = Router();

router.post("/create-task", authMiddleware, controller.createTask);

router.put("/update-task", authMiddleware, controller.updateTask);

export default router;

Controller Layer

Update:

src/modules/task/task.controller.js
const updateTask = async (req, res, next) => {
  try {
    const data = await taskService.updateTask(req);

    ApiResponse.ok(
      res,
      "Task updated successfully",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  updateTask
};

Business Logic (Service Layer)

Update:

src/modules/task/task.service.js
import ApiError from "../../common/utils/api-error.js";
import taskModel from "./task.model.js";

const updateTask = async (req) => {

    const { taskId, status } = req.body;

    // Step 1: Validate status
    const allowedStatus = ["Todo", "In Progress", "Done"];

    if (!allowedStatus.includes(status)) {
        throw ApiError.badRequest("Invalid status");
    }

    // Step 2: Find task (only owner can update)
    const task = await taskModel.findOne({
        _id: taskId,
        userId: req.userId
    });

    if (!task) {
        throw ApiError.notFound("Task not found");
    }

    // Step 3: Update status
    task.status = status;

    await task.save();

    return task;
};

export {
    updateTask
};

Request Flow

PUT /api/task/update-task
        ↓
auth.middleware.js (verify token)
        ↓
task.controller.js
        ↓
task.service.js
        ↓
MongoDB
        ↓
Response

Final Result: Testing api/task/update-task API Using Postman


Step 18: Delete Task

Now that tasks can be created and updated, the final step in the task lifecycle is deleting tasks — just like removing completed work in Trello.


Requirement

When deleting a task:

  • Identify the task using taskId

  • Ensure the task belongs to the logged-in user

  • If it exists → delete it from the database


Define Route

Update:

src/modules/task/task.routes.js
import { Router } from "express";
import authMiddleware from "../auth/auth.middleware.js";
import * as controller from "./task.controller.js";

const router = Router();

router.post("/create-task", authMiddleware, controller.createTask);
router.put("/update-task", authMiddleware, controller.updateTask);
router.delete("/delete-task", authMiddleware, controller.deleteTask);

export default router;

Controller Layer

Update:

src/modules/task/task.controller.js
const deleteTask = async (req, res, next) => {
  try {
    const data = await taskService.deleteTask(req);

    ApiResponse.ok(
      res,
      "Task deleted successfully",
      data
    );

  } catch (error) {
    next(error);
  }
};

export {
  deleteTask
};

Business Logic (Service Layer)

Update:

src/modules/task/task.service.js
import ApiError from "../../common/utils/api-error.js";
import taskModel from "./task.model.js";

const deleteTask = async (req) => {

    const { taskId } = req.body;

    // Step 1: Find task (only owner can delete)
    const task = await taskModel.findOne({
        _id: taskId,
        userId: req.userId
    });

    if (!task) {
        throw ApiError.notFound("Task not found");
    }

    // Step 2: Delete task
    await taskModel.deleteOne({ _id: taskId });

    return task;
};

export {
    deleteTask
};

Request Flow

DELETE /api/task/delete-task
        ↓
auth.middleware.js (verify token)
        ↓
task.controller.js
        ↓
task.service.js
        ↓
MongoDB
        ↓
Response

What Changed from Previous Approach?

Before:

  • All logic in one file

  • Hard to scale

Now:

  • Clean modular structure

  • Separation of concerns

  • Reusable logic

  • Easy to maintain


Complete Source Code

I’ve uploaded the complete project on GitHub. You can check it here:

GitHub Repo:
https://github.com/shubhamsinghbundela/Rest-API-Trello

More from this blog

Shubham Tech. Blog's

55 posts

Problem Solver | Currently Working As a Full Stack Developer, Community Leader At @Dev_Matrix | Previously Contributor at @RealDevSquad, @TeamShiksha