NestJS is a progressive Node.js framework for building scalable enterprise server-side applications using TypeScript, dependency injection, and modular architecture.
When to Use
- Enterprise Node.js Backend Microservices: Architecture inspired by Angular with modules, providers, and dependency injection.
- Domain-Driven Modular Applications: Structuring large codebases with clear domain separation and clean layering.
- Multi-Transport Microservices: Seamlessly switching between HTTP, gRPC, RabbitMQ, Kafka, and Redis transports.
- Type-Safe REST & GraphQL Backends: Combining TypeORM/Prisma, class-validator, and auto-generated Swagger documentation.
Quick Start
// cats.controller.ts
@Controller("cats")
export class CatsController {
constructor(private catsService: CatsService) {}
@Get() async findAll(): Promise<Cat[]> { return this.catsService.findAll(); } }
## Core Concepts
### Modular Architecture & Dependency Injection
Structuring modules, controllers, and injectable services:
```typescript
// users.service.ts
import { Injectable, NotFoundException } from "@nestjs/common";
export interface User {
id: string;
email: string;
}
@Injectable()
export class UsersService {
private users: User[] = [{ id: "1", email: "admin@example.com" }];
async findById(id: string): Promise<User> {
const user = this.users.find((u) => u.id === id);
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
}
// users.controller.ts
import { Controller, Get, Param } from "@nestjs/common";
@Controller("users")
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get(":id")
async getUser(@Param("id") id: string) {
return this.usersService.findById(id);
}
}
Request Validation with Pipes & Class-Validator
Validating incoming JSON payloads before controller execution:
// create-user.dto.ts
import { IsEmail, IsNotEmpty, MinLength } from "class-validator";
export class CreateUserDto {
@IsEmail({}, { message: "Invalid email address" })
email: string;
@IsNotEmpty()
@MinLength(8, { message: "Password must be at least 8 characters" })
password: string;
}
// main.ts
import { ValidationPipe } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Strip unvalidated properties
forbidNonWhitelisted: true,
transform: true,
}),
);
await app.listen(3000);
}
bootstrap();
Execution Guards & Role-Based Access Control (RBAC)
Protecting endpoints with custom authentication guards:
import {
Injectable,
CanActivate,
ExecutionContext,
UnauthorizedException,
} from "@nestjs/common";
import { Request } from "express";
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
const authHeader = request.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
throw new UnauthorizedException("Valid authorization token required");
}
// Attach verified user payload
request["user"] = { id: "usr_101", role: "admin" };
return true;
}
}
Common Patterns
Role-Based Access Control (RBAC) Guard with Reflector
Problem: Duplicating role checks across individual controller route handlers.
Solution: Use custom SetMetadata decorator and global RolesGuard:
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>(
"roles",
context.getHandler(),
);
if (!requiredRoles) return true;
const request = context.switchToHttp().getRequest();
const user = request.user;
return requiredRoles.some((role) => user?.roles?.includes(role));
}
}
Best Practices
Do:
- Enable
whitelist: trueandforbidNonWhitelisted: truein globalValidationPipeto prevent mass-assignment attacks. - Split applications into discrete domain modules (
UserModule,OrderModule,AuthModule) with explicit exports. - Use Fastify adapter (
@nestjs/platform-fastify) for performance-sensitive microservices. - Handle configuration with
@nestjs/configand validate environment variables with Joi or Zod.
Don't:
- Put business logic inside controllers; keep controllers strictly focused on request handling.
- Use circular dependencies between modules; use
forwardRef()only as an absolute last resort. - Catch exceptions silently without rethrowing or logging through Nest's
Loggerservice.
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
Nest can't resolve dependencies of the ... |
Required service or repository not listed in module providers or imports. |
Add the missing service to module providers or export it from its host module. |
Cannot find module '...' or its corresponding type declarations |
Path alias in tsconfig.json not recognized by Nest CLI runner. |
Verify baseUrl and paths mapping in both tsconfig.json and nest-cli.json. |
Circular dependency detected in ... |
Two services directly injecting each other in constructor. | Use forwardRef(() => OtherService) in both @Inject() annotations. |