backend
DTO validation as a real security boundary
DTO validation as a real security boundary
class-transformer turns an incoming JSON body into a real class instance; class-validator runs decorator-declared rules against it. Wired into NestJS's ValidationPipe, a controller declares its expected shape once as a class and gets request validation for free.
export class CreateApplicationDto {
@IsUUID()
program_id: string;
@IsOptional()
@IsUUID()
child_id?: string;
}
app.useGlobalPipes(new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true }));
The whitelist options are the point
whitelist: true strips any property not declared on the DTO before it reaches the service layer. forbidNonWhitelisted: true rejects the whole request instead of silently dropping the extras.
This is a real security boundary, not neatness. Without it, a client sending { ..., role: "admin" } on a user-update endpoint gets through if the service carelessly spreads the body into an update call. Mass assignment, closed off at validation rather than trusted to every individual handler to remember.
I learned the flip side of this the hard way: a DTO with import type instead of import, or a property with no decorator at all, isn't recognised under whitelist: true — so the pipe strips the entire payload and the endpoint rejects every field with a confusing "unrecognised property" error. I hit that shape of bug in three different files for three different reasons before I stopped fixing them one at a time and grepped the whole codebase for the pattern.
Beyond field checks: @ValidateNested() + @Type(() => NestedDto) for nested objects, @ValidateIf for conditional rules, and custom validators for domain-specific formats — a government ID pattern, a phone format — not just generic type checks. The DTOs double as documentation, and NestJS can generate Swagger straight from the decorators.