Lesson 14 of 5022 min read

NestJS Pipes Tutorial – Built-in and Custom Pipes Explained

Understand what pipes are in NestJS, how built-in pipes like ParseIntPipe and ValidationPipe work, and how to build your own custom pipe.

Author: CodersNexus

NestJS Pipes Tutorial – Built-in and Custom Pipes Explained

You have already used one of NestJS's most important pipes without necessarily calling it by name: ValidationPipe. Pipes, as a general concept, are one of the key building blocks in NestJS's request-handling pipeline, responsible for transforming and validating input data before it reaches a route handler.

This lesson zooms out from validation specifically to cover the full concept of pipes: what they are, the built-in pipes NestJS ships with, and how to build your own custom pipe when the built-in ones don't quite fit your needs.

NestJS Pipes: Learning Objectives

  • Define what a pipe is and where it fits in NestJS's request-handling pipeline.
  • Use built-in pipes like ParseIntPipe, ParseBoolPipe, and DefaultValuePipe.
  • Understand the difference between transformation pipes and validation pipes.
  • Build a custom pipe implementing the PipeTransform interface.
  • Apply pipes at the parameter, method, controller, or global level.

NestJS Pipes: Key Terms and Definitions

  • Pipe: A class implementing the PipeTransform interface, used to transform or validate data before it reaches a route handler.
  • PipeTransform interface: The contract a class must implement to function as a NestJS pipe, requiring a transform() method.
  • ParseIntPipe: A built-in pipe that converts a string input (like a route parameter) into an integer, throwing an error if conversion fails.
  • ArgumentMetadata: An object passed to a pipe's transform() method describing the parameter being processed, including its expected type.
  • BadRequestException: A built-in NestJS exception commonly thrown by pipes when input data fails a transformation or validation check.

How NestJS Pipes Works: Detailed Explanation

A pipe in NestJS is any class implementing the PipeTransform interface, which requires a single method: transform(value, metadata). This method receives the incoming value, whether it's a route parameter, query parameter, or request body, and returns either the transformed value or throws an exception if the data is invalid. Pipes run just before a route handler executes, meaning by the time your controller method's code actually runs, the data it receives has already been transformed or validated.

NestJS ships with several built-in pipes for common scenarios. ParseIntPipe converts a string value into an integer, throwing a BadRequestException automatically if the value cannot be parsed as a valid number, solving the exact problem from earlier lessons where route parameters always arrive as strings. ParseBoolPipe similarly converts string values like 'true' or 'false' into actual booleans. DefaultValuePipe supplies a fallback value when a parameter is not provided at all, commonly used together with an optional query parameter like page.

ValidationPipe, which you already used in the previous lesson, is technically also a pipe, but a more sophisticated one: instead of simple type conversion, it validates an entire object against a DTO's class-validator rules, throwing a BadRequestException with detailed error messages if any rule fails.

When the built-in pipes don't cover a specific need, you can build a custom pipe by creating a class marked with @Injectable() that implements PipeTransform. Inside its transform() method, you receive the raw value and can apply any custom logic: converting formats, enforcing custom business rules, or throwing a descriptive exception if the data doesn't meet your requirements. This is especially useful for cross-cutting transformation logic you want to reuse across many different routes without duplicating code.

Pipes can be applied at several different levels of granularity. Applied directly to a single parameter, such as @Param('id', ParseIntPipe) id: number, a pipe only affects that one value. Applied at the method level using @UsePipes(), a pipe affects all parameters of that route handler. Applied at the controller level, it affects every route within that controller. Applied globally in main.ts, as you did with ValidationPipe, it affects every route across the entire application.

Interview-Friendly Explanation

A strong interview or viva answer for nestjs pipes must go beyond a one-line definition. Interviewers evaluating BCA, MCA, B.Tech, and self-taught backend developers reward answers that combine a precise definition, a clear working explanation, a real-world example, and a conclusion that highlights why it matters in production Node.js applications. Use the four-point framework below when answering under time pressure.

  • Definition point: Pipe: A class implementing the PipeTransform interface, used to transform or validate data before it reaches a route handler.
  • Working point: Use built-in pipes like ParseIntPipe, ParseBoolPipe, and DefaultValuePipe.
  • Example point: E-commerce APIs commonly use ParseIntPipe on every numeric route parameter, such as product IDs and order IDs, to guarantee type safety without writing repetitive manual conversion code in every handler.
  • Conclusion point: Pipes solve the recurring problem of converting and validating data before it reaches your actual business logic.

How to Answer This in a Technical Interview

  1. Give a two-to-three sentence definition using correct NestJS and Node.js terminology.
  2. Add one specific example drawn from a real backend scenario such as an e-commerce, fintech, or SaaS API.
  3. Mention the request lifecycle stage this concept operates at (pipe, guard, middleware, filter, interceptor) since interviewers often test whether you know the execution order.
  4. Close with one benefit, trade-off, or production use case.
  5. Avoid vague answers like "it just validates stuff" — interviewers filter these out immediately.

Practical Scenario

Imagine you are hardening a production API for a SaaS product, similar to systems used at companies like Razorpay, Freshworks, or Postman. Every lesson in this module maps directly to a decision you will make while making that backend secure, predictable, and resilient to bad input: validating what comes in, transforming it safely, catching failures gracefully, and protecting routes from unauthorized access.

NestJS Pipes: Architecture and Flow Diagram

Visualize the request pipeline showing where pipes sit:

[Incoming Request] --> [Guards] --> [Interceptors (before)] --> [Pipes: transform/validate input] --> [Route Handler executes] --> [Interceptors (after)] --> [Response sent]

Add a note: 'Pipes run immediately before the route handler, ensuring the handler only ever receives clean, correctly-typed data.'

NestJS Pipes: Built-in Pipe Reference Table

Built-in PipePurposeExample Usage
ParseIntPipeConverts a string to an integer, errors if invalid@Param('id', ParseIntPipe) id: number
ParseBoolPipeConverts a string ('true'/'false') to a boolean@Query('active', ParseBoolPipe) active: boolean
ParseUUIDPipeValidates and parses a string as a UUID@Param('id', ParseUUIDPipe) id: string
DefaultValuePipeSupplies a default value if none is provided@Query('page', new DefaultValuePipe(1)) page: number
ValidationPipeValidates an entire object against DTO rulesapp.useGlobalPipes(new ValidationPipe())

NestJS Pipes: NestJS Code Example

import {
  Controller,
  Get,
  Param,
  Query,
  ParseIntPipe,
  DefaultValuePipe,
  PipeTransform,
  Injectable,
  BadRequestException,
} from '@nestjs/common';

// Using built-in pipes directly on parameters
@Controller('orders')
export class OrdersController {
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    // 'id' is guaranteed to be a real number here, or NestJS already returned a 400 error
    return { message: `Fetching order #${id}`, type: typeof id };
  }

  @Get()
  findAll(@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number) {
    return { message: `Fetching page ${page} of orders` };
  }
}

// A custom pipe: ensures a string is uppercase-formatted status
@Injectable()
export class UppercaseStatusPipe implements PipeTransform {
  private readonly allowed = ['PENDING', 'SHIPPED', 'DELIVERED', 'CANCELLED'];

  transform(value: string) {
    const upper = String(value).toUpperCase();
    if (!this.allowed.includes(upper)) {
      throw new BadRequestException(
        `Status must be one of: ${this.allowed.join(', ')}`,
      );
    }
    return upper;
  }
}

// Using the custom pipe
@Controller('orders')
export class OrdersStatusController {
  @Get('status/:status')
  findByStatus(@Param('status', UppercaseStatusPipe) status: string) {
    return { message: `Fetching orders with status ${status}` };
  }
}

The findOne method uses ParseIntPipe directly on the id parameter, guaranteeing that by the time the method body executes, id is genuinely a number, not a string, and automatically rejecting any non-numeric input with a 400 error before the handler even runs. The findAll method chains DefaultValuePipe and ParseIntPipe together, so a missing page query parameter defaults to 1, and whatever value results is still guaranteed to be converted to a number. Finally, UppercaseStatusPipe demonstrates a custom pipe enforcing a specific business rule, that a status value must be one of a fixed set of allowed values, throwing a clear, descriptive BadRequestException if it is not, and normalizing valid input to uppercase along the way.

Real-World NestJS Pipes Industry Examples

  • E-commerce APIs commonly use ParseIntPipe on every numeric route parameter, such as product IDs and order IDs, to guarantee type safety without writing repetitive manual conversion code in every handler.
  • Pagination systems across nearly all production APIs combine DefaultValuePipe and ParseIntPipe on page and limit query parameters, ensuring sensible defaults and correct types with minimal code.
  • Custom pipes are frequently built to validate domain-specific formats, such as a custom pipe validating that a route parameter matches a specific product SKU format before it reaches business logic.
  • Enum-based status fields, like order statuses or user roles, are often enforced through custom pipes similar to the UppercaseStatusPipe example, centralizing the allowed-values logic in one reusable place.

NestJS Pipes Interview Questions and Answers

Q1. What is a pipe in NestJS and where does it run in the request lifecycle?

Short answer: A pipe is a class implementing the PipeTransform interface, used to transform or validate incoming data before it reaches a route handler. Pipes run immediately before a matched route handler executes, ensuring the handler only receives correctly typed and validated data.

Detailed explanation: A pipe in NestJS is any class implementing the PipeTransform interface, which requires a single method: transform(value, metadata). This method receives the incoming value, whether it's a route parameter, query parameter, or request body, and returns either the transformed value or throws an exception if the data is invalid. Pipes run just before a route handler executes, meaning by the time your controller method's code actually runs, the data it receives has already been transformed or validated. NestJS ships with several built-in pipes for common scenarios. ParseIntPipe converts a string value into an integer, throwing a BadRequestException automatically if the value cannot be parsed as a valid number, solving the exact problem from earlier lessons where route parameters always arrive as strings. ParseBoolPipe similarly converts string values like 'true' or 'false' into actual booleans. DefaultValuePipe supplies a fallback value when a parameter is not provided at all, commonly used together with an optional query parameter like page. ValidationPipe, which you already used in the previous lesson, is technically also a pipe, but a more sophisticated one: instead of simple type conversion, it validates an entire object against a DTO's class-validator rules, throwing a BadRequestException with detailed error messages if any rule fails. When the built-in pipes don't cover a specific need, you can build a custom pipe by creating a class marked with @Injectable() that implements PipeTransform. Inside its transform() method, you receive the raw value and can apply any custom logic: converting formats, enforcing custom business rules, or throwing a descriptive exception if the data doesn't meet your requirements. This is especially useful for cross-cutting transformation logic you want to reuse across many different routes without duplicating code. Pipes can be applied at several different levels of granularity. Applied directly to a single parameter, such as @Param('id', ParseIntPipe) id: number, a pipe only affects that one value. Applied at the method level using @UsePipes(), a pipe affects all parameters of that route handler. Applied at the controller level, it affects every route within that controller. Applied globally in main.ts, as you did with ValidationPipe, it affects every route across the entire application.

Practical example: E-commerce APIs commonly use ParseIntPipe on every numeric route parameter, such as product IDs and order IDs, to guarantee type safety without writing repetitive manual conversion code in every handler.

Interview tip: Pipes implement PipeTransform with a transform(value, metadata) method and run just before the route handler.

Revision hook: Pipes solve the recurring problem of converting and validating data before it reaches your actual business logic.

Q2. What is the difference between a transformation pipe like ParseIntPipe and a validation pipe like ValidationPipe?

Short answer: ParseIntPipe focuses on converting a single value from one type to another, such as a string to a number, throwing an error if conversion is impossible. ValidationPipe operates on entire objects, checking them against a DTO's full set of class-validator decorator rules, which is a more comprehensive form of validation beyond simple type conversion.

Detailed explanation: The findOne method uses ParseIntPipe directly on the id parameter, guaranteeing that by the time the method body executes, id is genuinely a number, not a string, and automatically rejecting any non-numeric input with a 400 error before the handler even runs. The findAll method chains DefaultValuePipe and ParseIntPipe together, so a missing page query parameter defaults to 1, and whatever value results is still guaranteed to be converted to a number. Finally, UppercaseStatusPipe demonstrates a custom pipe enforcing a specific business rule, that a status value must be one of a fixed set of allowed values, throwing a clear, descriptive BadRequestException if it is not, and normalizing valid input to uppercase along the way.

Practical example: Pagination systems across nearly all production APIs combine DefaultValuePipe and ParseIntPipe on page and limit query parameters, ensuring sensible defaults and correct types with minimal code.

Interview tip: Key built-in pipes: ParseIntPipe, ParseBoolPipe, ParseUUIDPipe, DefaultValuePipe, ValidationPipe.

Revision hook: Built-in pipes like ParseIntPipe eliminate repetitive manual type-conversion code scattered across controllers.

Q3. How would you build a custom pipe in NestJS?

Short answer: You create an @Injectable() class that implements the PipeTransform interface, providing a transform(value, metadata) method that contains your custom logic, returning the transformed value if valid or throwing an exception, typically a BadRequestException, if the input fails your custom checks.

Detailed explanation: Pipes are classes implementing the PipeTransform interface, responsible for transforming or validating data immediately before it reaches a NestJS route handler. Built-in pipes like ParseIntPipe, ParseBoolPipe, and DefaultValuePipe solve common, everyday problems like converting string route parameters into numbers or supplying sensible fallback values, while ValidationPipe handles comprehensive object validation against DTO rules. When these built-in options are not enough, custom pipes let you encapsulate reusable, domain-specific transformation or validation logic, throwing clear exceptions like BadRequestException when input does not meet your requirements. Pipes can be applied at the parameter, method, controller, or global level, giving you precise control over exactly where each transformation or validation rule applies.

Practical example: Custom pipes are frequently built to validate domain-specific formats, such as a custom pipe validating that a route parameter matches a specific product SKU format before it reaches business logic.

Interview tip: Custom pipes are @Injectable() classes implementing PipeTransform, typically throwing BadRequestException on invalid input.

Revision hook: Custom pipes let you centralize reusable, domain-specific validation and transformation logic in one place.

Q4. At what levels can a pipe be applied in a NestJS application?

Short answer: Pipes can be applied at the parameter level (directly within a decorator like @Param('id', ParseIntPipe)), the method level (using @UsePipes() above a specific route handler), the controller level (applying to every route in that controller), or globally in main.ts, affecting the entire application.

Detailed explanation: A pipe in NestJS is any class implementing the PipeTransform interface, which requires a single method: transform(value, metadata). This method receives the incoming value, whether it's a route parameter, query parameter, or request body, and returns either the transformed value or throws an exception if the data is invalid. Pipes run just before a route handler executes, meaning by the time your controller method's code actually runs, the data it receives has already been transformed or validated. NestJS ships with several built-in pipes for common scenarios. ParseIntPipe converts a string value into an integer, throwing a BadRequestException automatically if the value cannot be parsed as a valid number, solving the exact problem from earlier lessons where route parameters always arrive as strings. ParseBoolPipe similarly converts string values like 'true' or 'false' into actual booleans. DefaultValuePipe supplies a fallback value when a parameter is not provided at all, commonly used together with an optional query parameter like page. ValidationPipe, which you already used in the previous lesson, is technically also a pipe, but a more sophisticated one: instead of simple type conversion, it validates an entire object against a DTO's class-validator rules, throwing a BadRequestException with detailed error messages if any rule fails. When the built-in pipes don't cover a specific need, you can build a custom pipe by creating a class marked with @Injectable() that implements PipeTransform. Inside its transform() method, you receive the raw value and can apply any custom logic: converting formats, enforcing custom business rules, or throwing a descriptive exception if the data doesn't meet your requirements. This is especially useful for cross-cutting transformation logic you want to reuse across many different routes without duplicating code. Pipes can be applied at several different levels of granularity. Applied directly to a single parameter, such as @Param('id', ParseIntPipe) id: number, a pipe only affects that one value. Applied at the method level using @UsePipes(), a pipe affects all parameters of that route handler. Applied at the controller level, it affects every route within that controller. Applied globally in main.ts, as you did with ValidationPipe, it affects every route across the entire application.

Practical example: Enum-based status fields, like order statuses or user roles, are often enforced through custom pipes similar to the UppercaseStatusPipe example, centralizing the allowed-values logic in one reusable place.

Interview tip: Pipes can be scoped at the parameter, method, controller, or global level.

Revision hook: Choosing the right scope for a pipe (parameter, method, controller, global) keeps your validation logic appropriately targeted.

NestJS Pipes MCQs and Practice Questions

1. Which interface must a class implement to function as a NestJS pipe?

  1. Injectable
  2. PipeTransform
  3. CanActivate
  4. NestMiddleware

Answer: B. PipeTransform

Explanation: A class must implement the PipeTransform interface, providing a transform() method, to be recognized and used as a valid pipe within NestJS's request-handling pipeline.

Concept link: Pipes implement PipeTransform with a transform(value, metadata) method and run just before the route handler.

Why this matters: Pipes solve the recurring problem of converting and validating data before it reaches your actual business logic.

2. What does ParseIntPipe do when applied to a route parameter?

  1. Converts the value to a boolean
  2. Converts the value to an integer, throwing an error if it fails
  3. Deletes the parameter entirely
  4. Encrypts the parameter value

Answer: B. Converts the value to an integer, throwing an error if it fails

Explanation: ParseIntPipe attempts to convert an incoming string value (such as a route parameter) into an integer, automatically throwing a BadRequestException if the value cannot be validly parsed as a number.

Concept link: Key built-in pipes: ParseIntPipe, ParseBoolPipe, ParseUUIDPipe, DefaultValuePipe, ValidationPipe.

Why this matters: Built-in pipes like ParseIntPipe eliminate repetitive manual type-conversion code scattered across controllers.

3. Which pipe would you use to supply a fallback value when a query parameter is missing?

  1. ParseIntPipe
  2. ParseBoolPipe
  3. DefaultValuePipe
  4. ValidationPipe

Answer: C. DefaultValuePipe

Explanation: DefaultValuePipe is specifically designed to provide a fallback value for a parameter when the client does not supply one, commonly combined with another pipe like ParseIntPipe for type conversion.

Concept link: Custom pipes are @Injectable() classes implementing PipeTransform, typically throwing BadRequestException on invalid input.

Why this matters: Custom pipes let you centralize reusable, domain-specific validation and transformation logic in one place.

4. What exception does a custom pipe typically throw when input data is invalid?

  1. NotFoundException
  2. BadRequestException
  3. InternalServerErrorException
  4. UnauthorizedException

Answer: B. BadRequestException

Explanation: BadRequestException is the conventional exception thrown by pipes (built-in and custom) when incoming data fails a transformation or validation check, resulting in an HTTP 400 response to the client.

Concept link: Pipes can be scoped at the parameter, method, controller, or global level.

Why this matters: Choosing the right scope for a pipe (parameter, method, controller, global) keeps your validation logic appropriately targeted.

Common NestJS Pipes Mistakes to Avoid

  • Manually converting route parameters to numbers inside a controller method instead of using ParseIntPipe, leading to repeated, inconsistent conversion logic across the codebase.
  • Forgetting that pipes run before the route handler, and trying to access already-transformed data through the raw request object instead of the pipe's transformed parameter.
  • Not throwing a proper NestJS exception (like BadRequestException) from a custom pipe, resulting in an unhandled error that produces a generic 500 response instead of a clear 400.
  • Overusing custom pipes for logic that would be better placed in a service, when the transformation is actually complex business logic rather than simple input shaping.

NestJS Pipes: Interview Notes and Exam Tips

  • Pipes implement PipeTransform with a transform(value, metadata) method and run just before the route handler.
  • Key built-in pipes: ParseIntPipe, ParseBoolPipe, ParseUUIDPipe, DefaultValuePipe, ValidationPipe.
  • Custom pipes are @Injectable() classes implementing PipeTransform, typically throwing BadRequestException on invalid input.
  • Pipes can be scoped at the parameter, method, controller, or global level.

Key NestJS Pipes Takeaways

  • Pipes solve the recurring problem of converting and validating data before it reaches your actual business logic.
  • Built-in pipes like ParseIntPipe eliminate repetitive manual type-conversion code scattered across controllers.
  • Custom pipes let you centralize reusable, domain-specific validation and transformation logic in one place.
  • Choosing the right scope for a pipe (parameter, method, controller, global) keeps your validation logic appropriately targeted.

NestJS Pipes: Summary

Pipes are classes implementing the PipeTransform interface, responsible for transforming or validating data immediately before it reaches a NestJS route handler. Built-in pipes like ParseIntPipe, ParseBoolPipe, and DefaultValuePipe solve common, everyday problems like converting string route parameters into numbers or supplying sensible fallback values, while ValidationPipe handles comprehensive object validation against DTO rules. When these built-in options are not enough, custom pipes let you encapsulate reusable, domain-specific transformation or validation logic, throwing clear exceptions like BadRequestException when input does not meet your requirements. Pipes can be applied at the parameter, method, controller, or global level, giving you precise control over exactly where each transformation or validation rule applies.

Frequently Asked Questions

Yes, technically ValidationPipe is itself a pipe implementing the PipeTransform interface, just like ParseIntPipe or any custom pipe you might write. It happens to be significantly more sophisticated, since it validates entire objects against a DTO's full set of class-validator rules rather than converting a single simple value. In interviews, tie this back to: Pipes implement PipeTransform with a transform(value, metadata) method and run just before the route handler. In real applications, consider this example: E-commerce APIs commonly use ParseIntPipe on every numeric route parameter, such as product IDs and order IDs, to guarantee type safety without writing repetitive manual conversion code in every handler. Key revision takeaway: Pipes solve the recurring problem of converting and validating data before it reaches your actual business logic.

ParseIntPipe automatically throws a BadRequestException, resulting in NestJS returning a 400 Bad Request response to the client, with a message indicating that the provided value could not be validly parsed as a number, without any additional code required from you. In interviews, tie this back to: Key built-in pipes: ParseIntPipe, ParseBoolPipe, ParseUUIDPipe, DefaultValuePipe, ValidationPipe. In real applications, consider this example: Pagination systems across nearly all production APIs combine DefaultValuePipe and ParseIntPipe on page and limit query parameters, ensuring sensible defaults and correct types with minimal code. Key revision takeaway: Built-in pipes like ParseIntPipe eliminate repetitive manual type-conversion code scattered across controllers.

Yes. You can chain multiple pipes on a single parameter by listing them together, such as @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, where NestJS applies them in order, first supplying a default value if missing, then converting the result to an integer. In interviews, tie this back to: Custom pipes are @Injectable() classes implementing PipeTransform, typically throwing BadRequestException on invalid input. In real applications, consider this example: Custom pipes are frequently built to validate domain-specific formats, such as a custom pipe validating that a route parameter matches a specific product SKU format before it reaches business logic. Key revision takeaway: Custom pipes let you centralize reusable, domain-specific validation and transformation logic in one place.

Write a custom pipe when the validation or transformation logic is generic enough to be reused across multiple routes or controllers and is fundamentally about shaping or checking input data, rather than executing core business logic, which belongs in a service instead. In interviews, tie this back to: Pipes can be scoped at the parameter, method, controller, or global level. In real applications, consider this example: Enum-based status fields, like order statuses or user roles, are often enforced through custom pipes similar to the UppercaseStatusPipe example, centralizing the allowed-values logic in one reusable place. Key revision takeaway: Choosing the right scope for a pipe (parameter, method, controller, global) keeps your validation logic appropriately targeted.

Guards run first to determine whether a request is even allowed to proceed, followed by interceptors (their 'before' logic), and then pipes run immediately before the route handler itself executes, ensuring the handler receives already-validated and transformed data. In interviews, tie this back to: Pipes implement PipeTransform with a transform(value, metadata) method and run just before the route handler. In real applications, consider this example: E-commerce APIs commonly use ParseIntPipe on every numeric route parameter, such as product IDs and order IDs, to guarantee type safety without writing repetitive manual conversion code in every handler. Key revision takeaway: Pipes solve the recurring problem of converting and validating data before it reaches your actual business logic.

Yes, since pipes marked with @Injectable() participate in NestJS's dependency injection system just like services, they can have other providers injected into their constructor, enabling more advanced validation logic that might, for example, need to check a database for uniqueness. In interviews, tie this back to: Key built-in pipes: ParseIntPipe, ParseBoolPipe, ParseUUIDPipe, DefaultValuePipe, ValidationPipe. In real applications, consider this example: Pagination systems across nearly all production APIs combine DefaultValuePipe and ParseIntPipe on page and limit query parameters, ensuring sensible defaults and correct types with minimal code. Key revision takeaway: Built-in pipes like ParseIntPipe eliminate repetitive manual type-conversion code scattered across controllers.