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.
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.tsThe 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,
}),
);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.