<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Thien Pham</title>
    <description>The latest articles on DEV Community by Thien Pham (@pvthiendeveloper).</description>
    <link>https://dev.to/pvthiendeveloper</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1414044%2F09c30524-b0e4-479a-992f-1c241177f60b.jpeg</url>
      <title>DEV Community: Thien Pham</title>
      <link>https://dev.to/pvthiendeveloper</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pvthiendeveloper"/>
    <language>en</language>
    <item>
      <title>Standardize and Validate Incoming Data Using Pipes in NestJS</title>
      <dc:creator>Thien Pham</dc:creator>
      <pubDate>Sat, 26 Oct 2024 04:05:19 +0000</pubDate>
      <link>https://dev.to/pvthiendeveloper/standardize-and-validate-incoming-data-using-pipes-in-nestjs-25dc</link>
      <guid>https://dev.to/pvthiendeveloper/standardize-and-validate-incoming-data-using-pipes-in-nestjs-25dc</guid>
      <description>&lt;p&gt;If you want to validate &amp;amp; transform the incoming Data before they go into the routes, then you can use Pipes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sample git repo: &lt;a href="https://github.com/pvthiendeveloper/nest-pipes" rel="noopener noreferrer"&gt;nest-pipes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;More information: &lt;a href="https://docs.nestjs.com/pipes" rel="noopener noreferrer"&gt;pipes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Class validator: &lt;a href="https://www.npmjs.com/package/class-validator" rel="noopener noreferrer"&gt;class-validator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Zod: &lt;a href="https://www.npmjs.com/package/zod" rel="noopener noreferrer"&gt;Zod&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Transform &amp;amp; Validate Incoming Data
&lt;/h2&gt;

&lt;h4&gt;
  
  
  1. Firstly, Create CatService
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export type Cat = {
  id: number;
  name: string;
};

@Injectable()
export class CatService {
  cats: Cat[] = [
    { id: 1, name: 'Cat 1' },
    { id: 2, name: 'Cat 2' },
    { id: 3, name: 'Cat 3' },
  ];

  findOne(id: number): Cat {
    return this.cats.find(cat =&amp;gt; cat.id === id);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Creates CatController
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Controller('cat')
export class CatController {
  constructor(private readonly catService: CatService) {}

  @Get(':id')
  getCat(@Param('id') id: number): Cat {
    return this.catService.findOne(id);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. First call, the response will be empty because the &lt;code&gt;id&lt;/code&gt; is a string &lt;code&gt;("1")&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwhhohyep0mws6fpo3hgi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwhhohyep0mws6fpo3hgi.png" alt="Image description" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Utilize &lt;code&gt;ParseIntPipe&lt;/code&gt; for Transformation and Validation
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; @Get(':id')
 getCat(@Param('id', ParseIntPipe) id: number): Cat {
   return this.catService.findOne(id);
 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The response is correct now. This means that when we use &lt;code&gt;ParseIntPipe&lt;/code&gt;, it will transform the param from &lt;code&gt;string&lt;/code&gt; to &lt;code&gt;number&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgg97k1ytw74daantowz5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgg97k1ytw74daantowz5.png" alt="Image description" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you pass a string &lt;code&gt;abc&lt;/code&gt;, the &lt;code&gt;ParseIntPipe&lt;/code&gt; will validate &amp;amp; throw an error back to the client. This means that the pipe validates for us.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9fljxjsnk1k6wdf0195l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9fljxjsnk1k6wdf0195l.png" alt="Image description" width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can also use Pipe for a &lt;code&gt;@Query()&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  @Get(':id')
  getCat(@Query('id', ParseIntPipe) id: number): Cat {
    return this.catService.findOne(id);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;There are many built-in Pipes:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- ValidationPipe
- ParseIntPipe
- ParseFloatPipe
- ParseBoolPipe
- ParseArrayPipe
- ParseUUIDPipe
- ParseEnumPipe
- DefaultValuePipe
- ParseFilePipe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Custom Pipes
&lt;/h2&gt;

&lt;h4&gt;
  
  
  1. Update CatService to add a phone for cats =))
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export type Cat = {
  id: number;
  name: string;
  phone?: string;
};

@Injectable()
export class CatService {
  cats: Cat[] = [
    { id: 1, name: 'Cat 1' },
    { id: 2, name: 'Cat 2' },
    { id: 3, name: 'Cat 3' },
  ];

  findOne(id: number): Cat {
    return this.cats.find(cat =&amp;gt; cat.id === id);
  }

  addPhone(id: number, phone: string): Cat {
    const cat = this.findOne(id);
    cat.phone = phone;
    return cat;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Create a custom pipe &lt;code&gt;PhoneValidatePipe&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ArgumentMetadata, PipeTransform } from '@nestjs/common';

export class PhoneValidatePipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (!value) {
      throw new Error('Phone number is required');
    }
    if (value.length !== 10) {
      throw new Error('Phone number must be 10 digits');
    }
    return '+' + value; // Transform
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Add a new method to &lt;code&gt;CatController&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; @Post(':id/phone')
  addPhone(
    @Param('id', ParseIntPipe) id: number,
    @Body('phone', new PhoneValidatePipe()) phone: string,
  ): Cat {
    return this.catService.addPhone(id, phone);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Check the response
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;With a wrong phone number&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhiwpxiiifds8xrvhbxci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhiwpxiiifds8xrvhbxci.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With a correct phone number&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa68l1jff81wmvl2a6hyt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa68l1jff81wmvl2a6hyt.png" alt="Image description" width="800" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;So, the Pipes is helping use to validate &amp;amp; transform the incoming data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Validate a Schema
&lt;/h2&gt;

&lt;p&gt;If you want to validate an object, there are two solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use class-validator&lt;/li&gt;
&lt;li&gt;Use Zod&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use class-validator
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm i --save class-validator class-transformer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1. Create a Dto
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { IsNotEmpty, IsOptional, IsString } from 'class-validator';

export class CreateCatDto {
  @IsString()
  @IsNotEmpty()
  name: string;

  @IsOptional()
  phone?: string;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Update CatController &amp;amp; CatService to add a new Cat
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;CatService
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  create(cat: CreateCatDto): Cat {
    const newCat = {
      id: this.cats.length + 1,
      ...cat,
    };
    this.cats.push(newCat);
    return newCat;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;CatController
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  @Post()
  async createCat(@Body() cat: CreateCatDto) {
    return this.catService.create(cat);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Create &lt;code&gt;RequestValidationPipe&lt;/code&gt; &amp;amp; extends it from &lt;code&gt;PipeTransform&lt;/code&gt; like the &lt;code&gt;PhoneValidatePipe&lt;/code&gt; above to validate and transform data
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ArgumentMetadata, PipeTransform } from '@nestjs/common';
import { validateOrReject, ValidationError } from 'class-validator';
import { plainToInstance } from 'class-transformer';

export class RequestValidationPipe implements PipeTransform {
  async transform(value: any, { metatype }: ArgumentMetadata) {
    try {
      // validateOrReject &amp;amp; plainToInstance to validate the incoming data
      await validateOrReject(plainToInstance(metatype, value));
    } catch (e) {
      // Format the message response to clients
      if (!(e instanceof Array)) throw e;
      const errors = e.map(errorItem =&amp;gt; {
        if (!(errorItem instanceof ValidationError)) throw e;
        return errorItem;
      });
      const message = errors
        .map(error =&amp;gt; Object.values(error.constraints))
        .join(', ');
      throw new Error(message);
    }
    return value;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Use the new custom pipes in the controller
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  @Post()
  @UsePipes(new RequestValidationPipe())
  async createCat(@Body() cat: CreateCatDto) {
    return this.catService.create(cat);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Result
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;It'll return error if there is missing fields&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5petc9agxx4f18025q2q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5petc9agxx4f18025q2q.png" alt="Image description" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Successfully response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5jo2e2zmferrrbk2bd8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm5jo2e2zmferrrbk2bd8.png" alt="Image description" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Zod
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  1. Create Zod Dto
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const createCatSchema = z
  .object({
    name: z.string(),
    phone: z.string().optional(),
  })
  .required();

export type ZodCreateCatDto = z.infer&amp;lt;typeof createCatSchema&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Create &lt;code&gt;ZodValidationPipe&lt;/code&gt; &amp;amp; still extends from &lt;code&gt;PipeTransform&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
  ArgumentMetadata,
  BadRequestException,
  PipeTransform,
} from '@nestjs/common';
import { ZodSchema } from 'zod';

export class ZodValidationPipe implements PipeTransform {
  constructor(private schema: ZodSchema) {}

  transform(value: unknown, metadata: ArgumentMetadata) {
    try {
      return this.schema.parse(value);
    } catch (error) {
      throw new BadRequestException('Validation failed');
    }
  }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3. Update CatController
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; @Post()
  @UsePipes(new ZodValidationPipe(createCatSchema))
  async createCat(@Body() cat: CreateCatDto) {
    return this.catService.create(cat);
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  4. Result
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Without name, it'll return the bad request message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4e0xmoqv3j4h78jboyf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4e0xmoqv3j4h78jboyf.png" alt="Image description" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With name, it'll return successful response&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3222ct3vaz3d305veu08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3222ct3vaz3d305veu08.png" alt="Image description" width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Both class-validator and Zod enhance data validation in NestJS by providing robust, flexible, and type-safe mechanisms, ultimately leading to cleaner code and better error management. Choosing between them often depends on specific project requirements and developer preferences.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
