Centralizing Request Validation in NestJS with a Custom TrimAndValidatePipe
When building REST APIs with NestJS, developers often place validation logic directly inside each controller method. This leads to duplicated code, inconsistent error messages, and makes it hard to evolve business rules.
05 Nov 2025, 14:07 UTC

The problem: scattered validation
When building REST APIs with NestJS, developers often place validation logic directly inside each controller method. This leads to duplicated code, inconsistent error messages, and makes it hard to evolve business rules as the service grows.
Solution: a reusable Pipe
Implementing TrimAndValidatePipe
Create a class that implements the PipeTransform interface. The pipe trims whitespace from string values, checks that the email field matches a simple regex, and throws a BadRequestException when the check fails.
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
@Injectable()
export class TrimAndValidatePipe implements PipeTransform {
private readonly emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
transform(value: any) {
if (typeof value === 'string') {
return value.trim();
}
if (value && typeof value === 'object') {
const trimmed = {};
for (const [key, val] of Object.entries(value)) {
trimmed[key] = typeof val === 'string' ? val.trim() : val;
}
// email validation if present
if (trimmed.email && !this.emailRegex.test(trimmed.email)) {
throw new BadRequestException('Invalid email format');
}
return trimmed;
}
return value;
}
}
Registering the pipe globally
In main.ts add the pipe before bootstrapping the application:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { TrimAndValidatePipe } from './pipes/trim-and-validate.pipe';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new TrimAndValidatePipe());
await app.listen(3000);
}
bootstrap();
Worked example: POST /users
Controller snippet
import { Controller, Post, Body } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
@Post()
create(@Body() dto: CreateUserDto) {
// dto is already trimmed and validated by the pipe
return { message: 'User created', data: dto };
}
}
The DTO can be simple:
export class CreateUserDto {
email: string;
name?: string;
}
Running the demo
- Install the NestJS CLI globally (requires npm write permissions):
npm i -g @nestjs/cli - Create a new project:
nest new pipe-demo - Navigate into the folder and add the pipe:
mkdir -p src/pipesthen createsrc/pipes/trim-and-validate.pipe.tswith the code above. - Add the DTO file
src/users/dto/create-user.dto.ts. - Replace the generated
src/users/users.controller.tswith the controller snippet. - Update
src/main.tsto register the pipe as shown. - Start the development server:
npm run start:dev - Send a request with curl (adjust
localhost:3000if your port differs):curl -X POST http://localhost:3000/users \ -H 'Content-Type: application/json' \ -d '{"email":" [contact removed] ","name":" Alice "}'
You should receive a JSON response where the email and name fields have leading/trailing spaces removed. If you send an malformed email, the pipe throws a BadRequestException and NestJS returns a 400 status with a message like 'Invalid email format'.
Trade‑off and limitation
Adding a pipe introduces a small per‑request overhead because NestJS invokes the transform method for every incoming payload. Keep the pipe lightweight; heavy validation (e.g., database look‑ups) should live in a service or a dedicated validation layer to avoid impacting throughput. Moreover, a global pipe can hide validation errors from developers who expect controller‑level checks; ensure exception messages are clear and consider logging the rejected payload for debugging.
How to verify
After each curl request, inspect the HTTP status code and response body. A successful trim yields a 200 OK with the cleaned data; an invalid email yields 400 Bad Request and the error message from the pipe. You can also add a simple console.log inside the pipe’s transform method to see when it runs during local development.
Actionable closing
If your NestJS API suffers from duplicated validation logic, extract it into a reusable Pipe like the one shown. Register it globally for consistent enforcement, keep the implementation focused, and monitor performance as traffic grows. This approach centralizes the source of truth, simplifies controller code, and improves testability.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.