NestJS

[NestJS] Pipes

NJ94 2023. 9. 6. 18:21

Pipe는 Controller arguments에 값을 전달받을떄 사용하며, 

아래의 2개의 처리를 함.

 

 

1. transformation: 입력 데이터를 원하는 형식으로 변환

2. Validation: 입력 데이터를 평가하고 유효한 경우 변경하지 않고 그대로 전달. 그렇지 않으면 예외 발생

 

 

😶 Pipe 동작 시점

Controller가 호출되기 전, Nest는 파이프를 삽입하고

파이프는 arguments를 수신하여 동작한다.

 

transformation, Validation 작업은 위의 시점에 수행되며,

그 후에는 경로 핸들러(Controller)가 호출 됨.

 

/pipe/a/123 호출 ->  "123" -> Pipe -> Controller 

 

 

😶 내장 파이프

 

ValidationPipe

ParseIntPipe

ParseFloatPipe

ParseBoolPipe

ParseArrayPipe

ParseUUIDPipe

ParseEnumPipe

DefaultValuePipe

ParseFilePipe

 

 

😶  Binding pipes

import { Controller, Get, HttpStatus, Param, ParseIntPipe, Query} from "@nestjs/common";

/**
 * https://docs.nestjs.com/pipes
 */

@Controller("/pipe")
export class PipeController {

    //http://localhost:3000/pipe/a/123
    @Get("/a/:id")
    pipeA(@Param("id", ParseIntPipe) id: number ) {
        console.log('id: ', id);
        console.log('id: ', typeof id);
        
        return "pipe test";
    }

    //http://localhost:3000/pipe/b/123
    @Get("/b/:id")
    pipeB(@Param("id", new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE })) id: number ) {
        console.log('id: ', id);
        console.log('id: ', typeof id);
        
        return "pipe test";
    }

    //http://localhost:3000/pipe/c?id=123
    @Get("/c")
    pipeC(@Query("id", new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE })) id: number ) {
        console.log('id: ', id);
        console.log('id: ', typeof id);
        
        return "pipe test";
    }

}

 

😶 Custom pipes

 

PipeTransform<T, R>

 

T : value

R: metadata

 

value: controller 실행 전, 매개변수

metadata: 매개변수의 메타데이터

 

Metadata의 속성 

export interface ArgumentMetadata {
  type: 'body' | 'query' | 'param' | 'custom';
  metatype?: Type<unknown>;
  data?: string;
}

 

import { ArgumentMetadata, Injectable, PipeTransform } from "@nestjs/common";

@Injectable()
export class ValidationPipe implements PipeTransform {
    transform(value: any, metadata: ArgumentMetadata) {
        return value;
    }
}

 

😶  Object schema validation

: 위의 custom pipe 파일을 생성 후, zod와 같이 사용하면 값에대한 유효성 검사가 편리하게 사용이 가능하다.

 

zod 설치

$ npm install --save zod

 

1. ValidationCustomPipe 파일 생성.

import { ArgumentMetadata, Injectable, PipeTransform, BadRequestException } from "@nestjs/common";
import { ZodObject } from 'zod';

@Injectable()
export class ValidationCustomPipe implements PipeTransform {

    constructor(private schema: ZodObject<any>) {}

    transform(value: any, metadata: ArgumentMetadata) {
        try {
            this.schema.parse(value);
        } catch(e) {
            throw new BadRequestException("Validation failed");
        }

        return value;
    }
}

 

2. zod 를 이용한 스키마 검증 파일 생성 

import { z } from 'zod';

export const sampleDtoSchema = z
  .object({
    name: z.string(),
    age: z.number(),
  })
  .required();

export type SampleDto = z.infer<typeof sampleDtoSchema>;

 

3. Controller에 아래와 같이 사용.

import { Body, Controller, Get, Post, HttpStatus, Param, ParseIntPipe, Query, UsePipes} from "@nestjs/common";

import { ValidationCustomPipe } from "src/common/pipes/validation.pipe";
import { sampleDtoSchema, SampleDto } from "./dto/sample.schema.dto";


/**
 * https://docs.nestjs.com/pipes
 */

@Controller("/pipe")
export class PipeController {

    @Post("/custom")
    @UsePipes(new ValidationCustomPipe(sampleDtoSchema))
    pipeCustom(@Body() dto: SampleDto) {
        console.log("dto: ", dto);

        return "pipe custom success";
    }

 

https://www.npmjs.com/package/zod

 

zod

TypeScript-first schema declaration and validation library with static type inference. Latest version: 3.22.2, last published: 18 days ago. Start using zod in your project by running `npm i zod`. There are 4945 other projects in the npm registry using zod.

www.npmjs.com

 

 

😶 Class Validator 

 

- TS만 사용가능하며, JS 사용하여 작성된 경우 사용 불가.

- npm Class Validator를 사용시, 유효성 검사를 편리하게 사용가능.

 

$ npm i --save class-validator class-transformer

 

import { ValidationPipe} from "@nestjs/common";

@Controller("/pipe")
export class PipeController {

    @Post("/custom/2")
    pipeCustom2(@Body(new ValidationPipe()) dto: SampleClassValidatorDto) {
        console.log("dto: ", dto);

        return "pipe custom success";
    }

 

import { IsString, IsInt } from 'class-validator';

export class SampleClassValidatorDto {
    @IsString()
    name: string;

    @IsInt()
    age: number;
}

 

 

 

ValidationPipe()

- 해당 검증 파이프는 Nest에서 기본적으로 제공되므로 구축할 필요가 없음

 

import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {

  async transform(value: any, { metatype }: ArgumentMetadata) {
    if (!metatype || !this.toValidate(metatype)) {
      return value;
    }
    
    const object = plainToInstance(metatype, value);
    const errors = await validate(object);
    
    if (errors.length > 0) {
      throw new BadRequestException('Validation failed');
    }

    return value;
  }

  private toValidate(metatype: Function): boolean {
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}

 

전역 범위 파이프

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

 

https://docs.nestjs.com/pipes

 

Documentation | NestJS - A progressive Node.js framework

Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Programming), FP (Functional Programming), and FRP (Functional Rea

docs.nestjs.com