Loading introduction
Loading stats
Loading about
Loading posts
Loading article
Skip to content
Md Nayeem Hossain
Explorer
home.tsx
nodejs-backend-architecture-patterns.md
nodejs-backend-architecture-patterns.md
blog.json

// Backend

Node.js Backend Architecture: Patterns for Production

Discover battle-tested architecture patterns for building robust and scalable Node.js backend applications.

Md Nayeem HossainDec 22, 202414 min read

Node.js Backend Architecture Patterns#

When building a backend with Node.js, the freedom it offers can also be a curse. Without a strict framework, code can easily become "spaghetti." To build a system that can scale and be maintained by a team, you need a solid architecture.

I prefer a Layered Architecture, also known as n-tier architecture. It separates concerns into distinct layers, making the codebase modular and testable.

The Layers#

  1. Controller Layer: Handles HTTP requests, parses inputs, and sends responses. It should not contain business logic.
  2. Service Layer: Contains the business logic. This is where the "thinking" happens. It doesn't know about HTTP (req/res).
  3. Repository/Data Access Layer: Handles communication with the database. It abstracts the SQL or MongoDB queries.

Here's how this looks in practice.

1. Controller Layer#

The controller is the entry point. It receives the request, calls the service, and returns the result.

typescript
1// controllers/user.controller.ts2import { Request, Response, NextFunction } from 'express';3import { UserService } from '../services/user.service';4 5export class UserController {6  constructor(private userService: UserService) {}7 8  async createUser(req: Request, res: Response, next: NextFunction) {9    try {10      // Validate input (very basic example)11      if (!req.body.email) throw new Error("Email required");12 13      // Call service14      const user = await this.userService.register(req.body);15 16      // Send response17      res.status(201).json({ success: true, data: user });18    } catch (error) {19      next(error); // Pass to error handling middleware20    }21  }22}

2. Service Layer#

The service layer contains your business rules. For example, "a user cannot register if the email already exists" or "password must be hashed."

typescript
1// services/user.service.ts2export class UserService {3  constructor(private userRepo: UserRepository) {}4 5  async register(userData: CreateUserDto) {6    // Check if user exists (Business Logic)7    const existing = await this.userRepo.findByEmail(userData.email);8    if (existing) {9      throw new AppError("User already exists", 400);10    }11 12    // Hash password (Business Logic)13    const hashedPassword = await bcrypt.hash(userData.password, 10);14 15    // Save to DB16    return this.userRepo.create({17      ...userData,18      password: hashedPassword19    });20  }21}

3. Repository Layer#

The repository handles the raw database commands. If you change your ORM or database later, you only need to update this layer.

typescript
1// repositories/user.repository.ts2export class UserRepository {3  async findByEmail(email: string) {4    return prisma.user.findUnique({ where: { email } });5  }6 7  async create(data: any) {8    return prisma.user.create({ data });9  }10}

Dependency Injection#

Notice how we pass the dependencies (like userService) into the classes. This is Dependency Injection. It makes your code extremely easy to test because you can pass "mock" versions of these dependencies during testing without needing a real database.

This architecture has served me well in applications serving millions of users.

#Node.js#Express#Architecture#API
OlderMongoDB Performance Optimization: Complete GuideNewer Next.js 14 App Router: The Complete Developer's Guide

Outline

  • Node.js Backend Architecture Patterns
  • The Layers
  • 1. Controller Layer
  • 2. Service Layer
  • 3. Repository Layer
  • Dependency Injection