Bordered avatar

Street Learner

Author
8 min read

Last Updated: a year ago

Mastering DTOs and Custom Pipes in NestJS: Clean, Safe, and Smart Data Handling

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:

cd nest-day6-pipes-dtos

We will create a module called products:

nest generate module products
nest generate controller products
nest generate service products

πŸ” Understanding DTOs (Data Transfer Objects)

DTOs define what the incoming data should look like.

You can think of a DTO as a contract between your API and the outside world:

  • What fields are required
  • What types they should be
  • What validation rules apply

To use DTOs properly, we need:

npm install class-validator class-transformer

πŸ“Œ Create: create-product.dto.ts

import { IsString, IsNumber, Min, Length } from 'class-validator';

export class CreateProductDto {
  @IsString()
  @Length(3, 50)
  name: string;

  @IsString()
  @Length(10, 200)
  description: string;

  @IsNumber()
  @Min(1)
  price: number;

  @IsNumber()
  @Min(0)
  stock: number;
}

βœ”οΈ What this DTO does:

  • Ensures all fields exist
  • Ensures string fields meet length requirements
  • Ensures price and stock are numbers
  • Rejects invalid requests before they reach your service

DTOs bring type safety, security, and readability.

πŸ”₯ Understanding Custom Pipes

A custom pipe in NestJS can:

  • Validate incoming values
  • Transform values before they reach your controller
  • Clean or sanitize data
  • Run dynamic logic based on the request

Unlike DTOs, pipes work on single values or entire request objects.

⚑ Build a Custom Pipe to Transform Price & Stock

Let’s say the frontend is sending numbers as strings:

{
  "name": "Laptop",
  "description": "High-performance machine",
  "price": "1200",
  "stock": "10"
}

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:

  1. Run the custom pipe β†’ transform data
  2. Validate with DTO β†’ ensure fields exist and meet rules
  3. Pass clean data to the service

This gives your backend full protection.

πŸ›’ Products Service

πŸ“Œ Update: products.service.ts

import { Injectable } from '@nestjs/common';
import { CreateProductDto } from './dto/create-product.dto';

@Injectable()
export class ProductsService {
  private products = [];

  create(product: CreateProductDto) {
    const newProduct = {
      id: this.products.length + 1,
      ...product,
    };

    this.products.push(newProduct);

    return {
      message: 'Product created successfully',
      product: newProduct,
    };
  }

  findAll() {
    return this.products;
  }
}

This gives you a working API that:

  • Creates products
  • Validates data
  • Transforms price and stock correctly
  • Returns the new product

🌍 Real E-Commerce Scenarios for DTOs & Pipes

πŸ“Œ DTO Use Cases

  • Validate user registration
  • Enforce strong password rules
  • Validate shipping addresses
  • Validate cart updates
  • Enforce minimum order amounts

πŸ“Œ Pipe Use Cases

  • Convert price strings to numbers
  • Ensure IDs are valid UUID or MongoDB ObjectIDs
  • Trim user input
  • Strip HTML tags from descriptions
  • Transform comma-separated categories into arrays

🧠 How Senior Developers Think About DTOs vs Pipes

βœ” DTOs are for:

  • Defining shape of data
  • Static validation
  • Type safety

βœ” Pipes are for:

  • Dynamic validation
  • Data transformation
  • Sanitization
  • Format conversions

βœ” Use them together:

This creates clean, scalable, and production-ready controller logic.

🏁 Final Thoughts

DTOs and Pipes are two of the most important tools in NestJS. Once you understand how to use them together, your API becomes:

  • More secure
  • Easier to maintain
  • More readable
  • More scalable

This is why senior NestJS developers rely on them heavily in real-world projects.

Related Stories



Performance Optimization of NestJS Applications: A Complete Guide

Performance Optimization of NestJS Applications: A Complete Guide

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.

Bordered avatar

Street Learner

21 Jan 2025

Mastering High-Performance Interceptors in NestJS for Scalable APIs

Mastering High-Performance Interceptors in NestJS for Scalable APIs

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.

Bordered avatar

Street Learner

18 Jan 2025