Pipes
A pipe is a class that an argument passes through before it reaches the controller method. A pipe can transform the value (the string "5" into the number 5) or check it and reject the request with a 400 error. The ValidationPipe from the previous lesson is a pipe too.
The built-in ParseIntPipe
Address parameters arrive as strings. ParseIntPipe turns a string into an integer:
// src/posts/posts.controller.ts
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';
import { PostsService } from './posts.service';
@Controller('posts')
export class PostsController {
constructor(private readonly postsService: PostsService) {}
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
return this.postsService.findOne(id);
}
}The pipe is the second argument of the decorator. Now id is a real number, and Number(id) is no longer needed. Open /posts/abc — instead of an empty response you get a 400 error saying Validation failed (numeric string is expected).
A default value
DefaultValuePipe substitutes a value when the parameter is missing. Pipes run from left to right:
// src/posts/posts.controller.ts
import { DefaultValuePipe, Get, ParseIntPipe, Query } from '@nestjs/common';
@Get()
findAll(@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number) {
return { page };
}/posts returns {"page":1}, and /posts?page=3 returns {"page":3}.
Built-in pipes
| Pipe | What it does |
|---|---|
ParseIntPipe | String to integer |
ParseFloatPipe | String to decimal number |
ParseBoolPipe | "true" and "false" to a boolean |
ParseUUIDPipe | Checks that the string is a UUID |
ParseEnumPipe | Checks that the value belongs to an enum |
DefaultValuePipe | Substitutes a default value |
ValidationPipe | Checks an object against DTO rules |
Your own pipe
A page number cannot be zero or negative. Create a pipe:
nest g pipe common/pipes/positive-int --flat// src/common/pipes/positive-int.pipe.ts
import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
@Injectable()
export class PositiveIntPipe implements PipeTransform {
transform(value: string): number {
const number = Number(value);
if (!Number.isInteger(number) || number < 1) {
throw new BadRequestException('A positive integer is expected');
}
return number;
}
}The pipe implements the PipeTransform interface: transform() receives the value and returns what ends up in the argument. A thrown BadRequestException becomes a 400 response.
A custom pipe is attached the same way as built-in ones:
// src/posts/posts.controller.ts
import { PositiveIntPipe } from '../common/pipes/positive-int.pipe';
@Get(':id')
findOne(@Param('id', PositiveIntPipe) id: number) {
return this.postsService.findOne(id);
}The address /posts/0 returns 400 with the message "A positive integer is expected".
Common mistake
Without a pipe, the number type on an argument changes nothing: TypeScript does not convert data at runtime, and id still holds a string. The comparison post.id === id then never matches, and the service returns an empty response. If an argument is declared as a number, it needs ParseIntPipe or transform: true in ValidationPipe.