Mastering DTOs and Custom Pipes in NestJS: Clean, Safe, and Smart Data Handling
When building APIs, especially at scale, one of the most important goals is making sure data coming from users is valid, clean, and properly transformed before your business logic handles it.
NestJS makes this easy with two powerful tools:
DTOs (Data Transfer Objects)
Custom Pipes
Together, they help you write clean, secure, and maintainable code, even in complex applications.
In todayβs guide, we will build a brand-new NestJS app, and learn how to use DTOs and custom pipes the right way.
Our example will focus on a simple Product Module in an e-commerce-style API.
β What You Will Learn Today
What DTOs really are
What Pipes are and why we need them
DTOs vs Pipes β when to use which
Building a new NestJS project
Creating DTOs for product creation
Building a custom pipe for transforming and validating data
Real e-commerce-style examples
Best practices used by senior developers
π Creating a Fresh NestJS Project
Run the following command:
nest new nest-day6-pipes-dtos
Choose npm or yarn as you prefer.
Now open the project:
If we directly send this to our database, it will break.
So we create a pipe that converts price and stock into numbers.
π Create: parse-product.pipe.ts
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
@Injectable()
export class ParseProductPipe implements PipeTransform {
transform(value: any) {
const transformed = { ...value };
// Convert price -> number
if (value.price) {
transformed.price = parseFloat(value.price);
if (isNaN(transformed.price)) {
throw new BadRequestException('Price must be a valid number');
}
}
// Convert stock -> number
if (value.stock) {
transformed.stock = parseInt(value.stock, 10);
if (isNaN(transformed.stock)) {
throw new BadRequestException('Stock must be a valid number');
}
}
return transformed;
}
}
βοΈ What this pipe does:
Converts strings like "1200" β 1200
Throws a clear error if invalid
Ensures your service always receives correct data types
π§ Using DTO + Pipe Together in Controller
This is where NestJS shines.
We combine both tools to create a safe and clean API endpoint.
π Update: products.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { ParseProductPipe } from './pipes/parse-product.pipe';
@Controller('products')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
@Post()
create(
@Body(new ParseProductPipe()) createProductDto: CreateProductDto,
) {
return this.productsService.create(createProductDto);
}
}
Now NestJS will:
Run the custom pipe β transform data
Validate with DTO β ensure fields exist and meet rules
GraphQL has changed how modern APIs are built. Instead of fixed REST endpoints, GraphQL allows clients to ask for exactly the data they need β no more and no less. When combined with NestJS and MongoDB, GraphQL becomes extremely powerful, scalable, and production-ready.
Optimizing the performance of a NestJS application is critical for building scalable, fast, and production-ready APIs. Even though NestJS is a high-performance framework, improper coding practices, unoptimized database queries, and lack of caching can slow down your application.
NestJS interceptors are one of the most powerful tools in the framework, enabling developers to transform responses, cache results, log performance, and optimize requests. For large-scale applications, building high-performance interceptors is essential to improve speed, maintainability, and scalability.