The decorators of the previous lessons -
@IsString, @IsNumber, @Min - check nothing by themselves. They are a description of requirements pinned to fields, like a manifest pinned to a crate. Somebody must take that manifest in hand and compare it with the contents.The main role of
is automatically running class-validator decorator validation. It does not encrypt data in HTTP requests, does not compress server responses, and does not generate Swagger documentation. It does one thing: it takes the incoming object, reads the decorators off the DTO class, and checks whether the data answers to them.ValidationPipe
The pipe is registered once, at application startup:
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule);
3
4 app.useGlobalPipes(
5 new ValidationPipe({
6 whitelist: true,
7 forbidNonWhitelisted: true,
8 transform: true,
9 }),
10 );
11
12 await app.listen(3000);
13}The notation reads in four parts:
is the application object, app
opens the registration, .useGlobalPipes(
creates the pipe with its options, and new ValidationPipe({ ... })
closes the call.)
Note the plural in the method's name:
useGlobalPipes accepts several pipes separated by commas. In practice you almost always pass one - ValidationPipe - because the others, such as ParseIntPipe, only make sense on particular parameters.The pipe's configuration is not a set of independent switches but a ladder of strictness. It is worth knowing whole, because choosing a rung is a decision about how your API treats unknown data.
ValidationPipe - no validation. Any JSON object reaches the handler. Type annotations in the code are then a wish, not a guarantee.ValidationPipe() - basic validation. The decorators' rules apply, but fields outside the DTO pass through untouched.whitelist: true - removing unknown fields.whitelist + forbidNonWhitelisted - rejecting requests.One thing separates the last two, and it is worth remembering exactly. The
option automatically removes fields without validation decorators from incoming data. It does not add a white background to responses, does not create a list of allowed IP addresses, and does not disable validation on chosen endpoints - it simply cuts out whatever you did not describe in the DTO.whitelist: true
The difference from
is that forbidNonWhitelisted: true
removes unknown fields silently, whereas whitelist
returns a forbidNonWhitelisted
error. The two are not identical, 400
forbidNonWhitelisted disables nothing, and it adds no fields to the server logs.1// The DTO describes only: name, cohortId
2// The client sends: { name: 'Marcus', cohortId: 3, isAdmin: true }
3
4// ValidationPipe() -> the service receives isAdmin: true
5// whitelist: true -> the service receives { name, cohortId }
6// whitelist + forbidNonWhitelisted -> 400 Bad RequestThe
isAdmin field in that example is no accident. Without whitelist it reaches the service, and if somewhere further along somebody writes Object.assign(user, dto), the client has just granted itself privileges. It is the oldest way of taking over an account in applications that trust their input.Which mode to choose?
forbidNonWhitelisted is stricter and by that token more honest: the client learns it sent something unexpected, instead of assuming you saved its field. Plain whitelist can be more convenient on a public API where older clients still send fields since withdrawn.The last option of the production set concerns types.
makes the handler receive an instance of the DTO class rather than a plain object - and converts simple types along the way. Without it transform: true
@Param('id') id: number gives you the string '7', though the type says number, because everything arrives from a URL as text.It is also the condition for
@Type and @Transform from class-transformer to work: without transform: true those remain annotations with no effect.When one endpoint needs different rules from the rest, the pipe is attached locally through
@UsePipes(new ValidationPipe({ ... })) above the method. A local pipe takes precedence over the global one.Reach for this rarely. Validation configuration scattered across controllers soon stops being legible, and answering "does this endpoint reject extra fields?" begins to require reading three files.
A manifest checks nothing by itself, @name:
ValidationPipe is automatically running class-validator decorator validation - not encryption, not compression, not generating Swagger,app → .useGlobalPipes( → new ValidationPipe({ ... }) → ),ValidationPipe → ValidationPipe() → whitelist: true → whitelist + forbidNonWhitelisted,whitelist: true removes fields without validation decorators from incoming data - it has nothing to do with IP addresses or disabling validation,whitelist removes unknown fields silently, forbidNonWhitelisted returns a 400 error,isAdmin is the simplest road to granting yourself privileges,transform: true yields a DTO instance and converts simple types; without it @Type and @Transform do nothing,@UsePipes above a method overrides the global configuration - use it sparingly.In the next lesson we take up
class-transformer - what happens to data in the other direction, on its way out of the API. For now remember: decorators describe, the pipe executes. Without it you have very precise documentation of rules nobody enforces.