T33: Nest.js Architecture

Nest.js organizes a Node.js app into modules. Each module groups controllers (handle HTTP), services (do the work), and dependency injection (wires them together). Raw Node.js from T21 works for small apps. At scale you need structure - Nest.js gives it to you with enforced patterns and first-class testing support.

Modules, Controllers, Services

Every Nest.js app is organized into modules. Each module groups related controllers (handle HTTP) and services (handle business logic). Decorators like @Controller and @Injectable tell the framework what each class does.

// menu.module.ts
import { Module } from "@nestjs/common";
import { MenuController } from "./menu.controller";
import { MenuService } from "./menu.service";

@Module({
    controllers: [MenuController],
    providers: [MenuService],
})
export class MenuModule {}

// menu.controller.ts
import { Controller, Get, Post, Body } from "@nestjs/common";
import { MenuService } from "./menu.service";

@Controller("menu")
export class MenuController {
    constructor(private readonly menuService: MenuService) {}

    @Get()
    findAll() {
        return this.menuService.findAll();
    }

    @Post()
    create(@Body() body: { name: string; price: number }) {
        return this.menuService.create(body);
    }
}

// menu.service.ts
import { Injectable } from "@nestjs/common";

@Injectable()
export class MenuService {
    private items = [
        { id: 1, name: "Tonkotsu Ramen", price: 850 },
    ];

    findAll() {
        return this.items;
    }

    create(data: { name: string; price: number }) {
        const item = { id: this.items.length + 1, ...data };
        this.items.push(item);
        return item;
    }
}

Dependency Injection

The controller does not create its own service. It declares what it needs in the constructor, and the framework provides it. This makes testing easy - you can swap in mock services without changing controller code.

// The controller declares its dependency
constructor(private readonly menuService: MenuService) {}

// Nest.js automatically creates and injects the MenuService instance
// In tests, you can provide a mock instead:
// { provide: MenuService, useValue: mockMenuService }

Decorators and TypeScript

Nest.js uses TypeScript decorators extensively. @Controller, @Get, @Post, @Body, @Injectable - these annotations define behavior without cluttering your logic.

flowchart TD A[HTTP Request] --> B["@Controller: MenuController"] B -->|"@Get /menu"| C["@Injectable: MenuService"] C -->|getAll| D[(Database)] D -->|results| C C -->|data| B B -->|JSON response| A subgraph MenuModule B C end style A fill:#4a90d9,stroke:#2a5f8f,color:#1a1a1a style B fill:#e74c3c,stroke:#a93226,color:#1a1a1a style C fill:#6ab04c,stroke:#3d7a28,color:#1a1a1a style D fill:#f9ca24,stroke:#c9a31e,color:#1a1a1a

Key Takeaways

  • Nest.js enforces structure with modules, controllers, and services as the three pillars
  • Controllers handle HTTP routing, services handle business logic - never mix them
  • Dependency injection lets the framework wire components together, simplifying testing
  • TypeScript decorators define behavior declaratively without cluttering your logic