When building real-world APIs, things don’t break because of complex logic—they break because of bad input and inconsistent error handling.
Imagine:
This is where validation and error handling in NestJS become critical.
Mental Model
Client → DTO Validation → Controller → Service → Exception Filter → Response
Validation is not optional—it’s your first line of defense.
NestJS uses:
class-validator → validate data. class-transformer → transform payloadsInstall them:
npm install class-validator class-transformer
DTO = contract between client and server
Create create-user.dto.ts:
import { IsEmail, IsNotEmpty } from 'class-validator';export class CreateUserDto {@IsNotEmpty()name: string;@IsEmail()email: string;}
@IsNotEmpty() → ensures name is provided. @IsEmail() → ensures email is valid| Category | Decorator | Description | Example Use |
|---|---|---|---|
| String | @IsString() | Must be a string | name, title |
| String | @IsNotEmpty() | Cannot be empty | name, password |
| String | @Length(min,max) | Limit string length | username |
@IsEmail() | Valid email format | ||
| Format | @Matches(regex) | Match custom pattern | username, password |
| Number | @IsInt() | Must be an integer | age, page |
| Number | @Min(n) | Minimum value | page ≥ 1 |
| Number | @Max(n) | Maximum value | limit ≤ 100 |
| Boolean | @IsBoolean() | Must be true/false | isActive |
| Date | @IsDateString() | Valid ISO date string | startDate |
| Optional | @IsOptional() | Field is optional | update DTO |
| Array | @IsArray() | Must be an array | tags |
| Array | @ArrayNotEmpty() | Array cannot be empty | tags |
| Nested | @ValidateNested() | Validate nested object | profile object |
Update main.ts:
import { ValidationPipe } from '@nestjs/common';app.useGlobalPipes(new ValidationPipe({whitelist: true,forbidNonWhitelisted: true,transform: true,}););
Validate at the boundary, not inside business logic.
@Controller('users')export class UserController {constructor(private readonly userService: UserService) {}@Get()findAll(): Promise<User[]> {return this.userService.findAll();}@Post()create(@Body() createUserDto: CreateUserDto): Promise<User> {return this.userService.create(createUserDto);}}
👉 What happens now:
NestJS provides built-in exceptions:
BadRequestExceptionNotFoundExceptionUnauthorizedExceptionif (existing) {throw new BadRequestException('Email already exists');}
👉 Why:
For consistent API responses, create:
@Catch()export class HttpExceptionFilter implements ExceptionFilter {catch(exception: unknown, host: ArgumentsHost) {const ctx = host.switchToHttp();const response = ctx.getResponse();const status =exception instanceof HttpException? exception.getStatus(): 500;const message =exception instanceof HttpException? exception.getResponse(): 'Internal server error';response.status(status).json({statusCode: status,message,timestamp: new Date().toISOString(),});}}
app.useGlobalFilters(new HttpExceptionFilter());