What is a Pipe

Transform and Validate

A pipe receives one handler argument (body, param or query) before the handler runs. It either returns a (possibly converted) value or throws an exception, usually BadRequestException.

  • Transformation: "7" becomes 7, "true" becomes true.
  • Validation: reject bad input with a 400 before your code sees it.
@Controller('products')
export class ProductsController {
  // pipe class: NestJS creates (and reuses) the instance
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {}

  // pipe instance with options
  @Get(':id/stock')
  stock(@Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE })) id: number) {}
}

Pipes are Chained

Example

Pipes for one argument run left to right, each receiving the previous output. Here is the same idea with plain functions, mimicking @Query('page', new DefaultValuePipe(1), ParseIntPipe):

class BadRequestException extends Error {}
type Pipe = (value: unknown) => unknown;

const defaultValue = (def: unknown): Pipe => (v) => (v === undefined || v === '' ? def : v);
const parseInt: Pipe = (v) => {
  if (typeof v === 'number') return v;
  if (typeof v !== 'string' || !/^-?\d+$/.test(v)) throw new BadRequestException('Validation failed (numeric string is expected)');
  return Number(v);
};
const min = (n: number): Pipe => (v) => {
  if ((v as number) < n) throw new BadRequestException('page must not be less than ' + n);
  return v;
};

function applyPipes(value: unknown, pipes: Pipe[]): unknown {
  return pipes.reduce((acc, pipe) => pipe(acc), value);
}

for (const raw of [undefined, '3', 'abc', '-2']) {
  try {
    const page = applyPipes(raw, [defaultValue(1), parseInt, min(1)]);
    console.log(JSON.stringify(raw), '->', page, typeof page);
  } catch (e) {
    console.log(JSON.stringify(raw), '-> 400', (e as Error).message);
  }
}

Output

undefined -> 1 number
"3" -> 3 number
"abc" -> 400 Validation failed (numeric string is expected)
"-2" -> 400 page must not be less than 1