When building an API, one of the first lines of defense you must establish is strong input validation. This not only improves the user experience by providing clear error messages, but also protects your application from unexpected or malicious data.
I'll show you how to implement input validation in NestJS in a clean, reusable, and efficient way.
📦 What will we need?
NestJS works perfectly with two powerful libraries:
- class-validator: to apply validation rules.
- class-transformer: to transform incoming data into class instances.
First, let's install them:
npm install class-validator class-transformer
📐 Step 1: Define a DTO with validation rules
A DTO (Data Transfer Object) is the cleanest and most declarative way to define what data you expect and how it should be validated.
// src/users/dto/create-user.dto.ts
import { IsString, IsEmail, IsInt, MinLength, IsNotEmpty } from 'class-validator';
export class CreateUserDto {
@IsString()
@IsNotEmpty()
@MinLength(2)
name: string;
@IsEmail()
email: string;
@IsInt()
age: number;
}
🧱 Step 2: Use the DTO in your controller
Now, simply use it in your controller. NestJS will automatically validate the request data… but only if we enable the validation pipe (next step).
// src/users/users.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
@Post()
createUser(@Body() dto: CreateUserDto) {
return {
message: `User ${dto.name} registered successfully.`,
};
}
}
⚙️ Step 3: Enable the ValidationPipe globally
To automatically validate every incoming request, configure the ValidationPipe globally inside your main.ts.
// src/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({
transform: true, // Automatically transforms types
whitelist: true, // Strips properties not defined in the DTO
forbidNonWhitelisted: true, // Throws an error if unknown properties are present
}));
await app.listen(3000);
}
bootstrap();
🚨 What happens if validation fails?
NestJS will automatically throw a BadRequestException. The client will receive a well-structured error response like this:
{
"statusCode": 400,
"message": [
"name must be a string",
"email must be an email",
"age must be an integer"
],
"error": "Bad Request"
}
🧰 Bonus: Validation for query, params, and headers
You can also use DTOs to validate data inside @Query(), @param(), or @Headers():
@Get()
getUserByEmail(@Query() query: GetUserByEmailDto) {
return this.userService.findByEmail(query.email);
}
🏁 Conclusion
Input validation is one of the most powerful ways to keep your API robust, secure, and easy to maintain. Thanks to the integration of class-validator and class-transformer, NestJS allows us to do it in a very simple, clean, and maintainable way.
Validating data is not just a technical concern. It’s about taking care of your user experience, your application’s security, and its long-term stability.
Top comments (0)