All articles
ArchitectureFebruary 15, 20267 min read

How I Structure a NestJS Application for Scale

The folder structure you choose determines how well your NestJS codebase scales. Here's the pattern I've settled on after building multiple production APIs.

NestJSTypeScriptArchitectureClean CodeBackend

NestJS gives you a lot of freedom in how you organize a project. That freedom is both an asset and a trap. Early structure decisions compound over time — a poorly organized codebase at 20 endpoints becomes genuinely painful at 80.

Module-first thinking

Every feature lives in its own module. The module boundary defines what's public (exported) and what's private (internal). This forces you to be intentional about dependencies between features.

src/
├── auth/
│   ├── auth.module.ts
│   ├── auth.controller.ts
│   ├── auth.service.ts
│   ├── strategies/
│   ├── guards/
│   └── dto/
├── users/
├── prisma/
└── app.module.ts

The three-layer rule

  • Controller: handles HTTP — parses the request, calls the service, returns the response. No business logic.
  • Service: contains all business logic — no HTTP concepts, no direct database calls.
  • PrismaService: injected into services for all database access.

DTOs with class-validator

import { IsString, IsNotEmpty, MaxLength } from 'class-validator';

export class CreatePostDto {
  @IsString() @IsNotEmpty() @MaxLength(200)
  title: string;

  @IsString() @IsNotEmpty()
  content: string;
}

Global exception filter

app.useGlobalPipes(
  new ValidationPipe({
    whitelist: true,
    forbidNonWhitelisted: true,
    transform: true,
  }),
);
NOTE

Register global pipes, filters, and interceptors in main.ts rather than on individual controllers. It removes boilerplate and ensures consistent behavior across the entire API surface.