ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 230105목_공부일지
    > Backend/NestJS 2023. 1. 5. 17:36

    1. 파이프와 유효성 검사: 요청이 제대로 전달되었는가 (p 113~)

       => 정의: 요청이 라우터 핸들러로 전달되기 전에 요청 객체를 변환할 수 있는 기회를 제공

       => 미들웨어와 유사하나, 미들웨어는 app의 모든 컨텍스트에서 사용하도록 할 수 없음. 

       => 목적: 변환(입력 데이터를 원하는 형시으로 변환) && 유효성 검사 (입력 데이터가 사용자가 정한 기준에 유효하지 않은 경우 예외)

       => 사용방법: @Param decorator의 두번째 인수로 파이프를 넘겨 현재 실행 콘텍스트 (ExecutionContext에 바인딩)

     

    findOne(@Param('id', new ParseIntPipe({errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE})) id: number) {
    

     

    => 숫자일때는 정상
    // CMD:: curl -X GET http://localhost:3000/origin/22
    // RESULT:: This action returns a #22 user%
    
    => 문자일때는 400에러
    // CMD:: curl -X GET http://localhost:3000/users/origin/ww
    // RESULT:: {"statusCode":400,"message":"Validation failed (numeric string is expected)","error":"Bad Request"}%

     

    2. DefaultValuePipe는 인수의 값에 기본값을 설정할 때 사용. 

    // CMD:: curl -X GET http://localhost:3000/origin
    // RESULT:: This action returns all users offset:: 0 & limit:: 10%
    @Get('/origin')
    findAll(
        @Query('offset', new DefaultValuePipe(0), ParseIntPipe) offset: number,
        @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number
    ) {
        return this.usersService.findAll(offset, limit);
    }

     

    3. 파이프의 내부 구현 및 이해 

    import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
    import { plainToClass } from 'class-transformer';
    import { validate } from 'class-validator';
    
    @Injectable()
    export class ValidationPipe implements PipeTransform<any> {
        // 구현되어야 하는 transform은 두개의 parameter 있음.
        // 1. value: 현재 pipe로 전달되는 인수
        // 2. metadata: 현재 파이프에 전달되는 인수의 메타데이터
          transform(value: any, metadata: ArgumentMetadata): any {
        
             return value;
         }
         
            // ArgumentMetadata  정의
            // type: 파이프에 전달된 param의 종류 (Body, Param, Query)
            // metatype:: 라우트 핸들러에 정의된 인수의 타입/ 핸들러에서 타입을 생략하거나 바닐라 사용시 undefined
            // data:: decorator에 전달된 문자열 => 매개변수의 이름
    
    
    }

     

    // CMD:: curl -X GET http://localhost:3000/origin/pipe/23
    // RESULT:: This action returns a #22 user%
    // CONSOLE:: { metatype: [Function: Number], type: 'param', data: 'id' }
    @Get('/origin/pipe/:id')
    findOne_validationPipe(@Param('id', ValidationPipe) id: number) {
        return this.usersService.findOne(+id);
    }

     

    4. 유효성 검사 파이프 만들기 (using class-validator && class-transformer)

       => npm i --save class-validator class-transformer

     

    => create-user-dto.ts

    // NestJS에서 payload 를 처리하기 위해서는 데이터 전송 객체 (DTO)를 구현하면 된다.
    import {IsString, MaxLength, MinLength, IsEmail } from "class-validator";
    
    
    export class CreateUserDto {
        @IsString()
        @MinLength(1)
        @MaxLength(20)
        readonly name: string;
    
    
        @IsEmail()
        readonly email: string;
    
        readonly password: string;
    
    }

     

    => validation.pipe.ts

    import { ArgumentMetadata, BadRequestException, Injectable, PipeTransform } from '@nestjs/common';
    import { plainToClass } from 'class-transformer';
    import { validate } from 'class-validator';
    
    @Injectable()
    export class ValidationPipe implements PipeTransform<any> {
        // 구현되어야 하는 transform은 두개의 parameter 있음.
        // 1. value: 현재 pipe로 전달되는 인수
        // 2. metadata: 현재 파이프에 전달되는 인수의 메타데이터
        // before::
        // transform(value: any, metadata: ArgumentMetadata): any {
        //
        //     return value;
        // }
    
        // after::
        // CMD :: http://localhost:3000/origin/create -H "Content-Type: application/json" -d '{"name": "name_example", "email":"email@example.com"}'
        // RESULT:: {"name":"name_example","email":"email@example.com"}%
        async transform(value: any, { metatype }: ArgumentMetadata) {
            // ArgumentMetadata  정의
            // type: 파이프에 전달된 param의 종류 (Body, Param, Query)
            // metatype:: 라우트 핸들러에 정의된 인수의 타입/ 핸들러에서 타입을 생략하거나 바닐라 사용시 undefined
            // data:: decorator에 전달된 문자열 => 매개변수의 이름
    
            if (!metatype || !this.toValidate(metatype)) {
                return value;
            }
            //take  entity and turn it into  instant
            const object = plainToClass(metatype, value);
            const errors = await validate(object)
            if (errors.length > 0) {
                throw new BadRequestException('Validation failed');
            }
            return value
        }
        // 설명::
        // 전달된 meta-type 이 파이프가 지원하는 타입인지 검사. (toValidate)
        // class-transformer  plainToClass 함수를 통해 순수 자바스크립트 객체를 클래스의 객체로 변경 => 캡슐화 진행
        // (더이상 리터럴 객체를 다룰 필요 없이 값과 행위가 한곳에 모여있는 클래스 인스턴스 단위로 다룰 수 있게 됩니다.)
        // class-validator 유효성 검사 데커레이터는 타입이 필요. (이를 위해서, plainToClass 사용 )
        // 네트워크 요청을 통해 들어온 데이터는 역직렬화 과정에서 본문의 객체가 아무런 타입 정보도 가지고 있지 않기 떄문에, 타입을 지정하는 변환과정을 plainToClass로 수행
        // 마지막으로 유효성 검사에 통과했다면, 원래 값 그대로 전달  || 실패시 400 에러
    
        private toValidate(metatype: Function): boolean {
            const types: Function[] = [String, Boolean, Number, Array, Object];
            return !types.includes(metatype);
        }
    
    
    }

     

     

    => ValidationPipe 적용하기 위해 1) main.ts에 추가 및 2) create핸들러에 적용 

     

    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    import { ValidationPipe } from "./pipe/validation/validation.pipe";
    import * as dotenv from 'dotenv';
    import * as path from 'path';
    
    // dotenv.config({
    //   path: path.resolve(
    //       (process.env.NODE_ENV === 'production') ? './env/.production.env'
    //           :(process.env.NODE_ENV === 'stage') ? './env/.stage.env' : './env/.development.env'
    //   )
    // })
    
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      // ValidationPipe를 모든 핸들러에 일일이 지정하지 않고, 전역으로 설정하려면 부트스트랩 과정에서 적용 => nest에 이미 validation-pipe가 있기 때문에 직접 만들필요 X
         app.useGlobalPipes(new ValidationPipe())
    
      await app.listen(3000);
    }
    bootstrap();
    @Post('/origin/create')
    create(@Body(ValidationPipe) createUserDto: CreateUserDto) {
        return this.usersService.create(createUserDto)
    }

     

     

    5. 유저 서비스에 유효성 검사 적용

       => 기존에 생성한 ValidationPipe은 이해를 위해서 만들고 사용했기 때문에, 이제는 @nestjs/common 에 있는 ValidationPipe사용

    import { NestFactory } from '@nestjs/core';
    import { AppModule } from './app.module';
    // import { ValidationPipe } from "./pipe/validation/validation.pipe";
    import { ValidationPipe } from "@nestjs/common";
    import * as dotenv from 'dotenv';
    import * as path from 'path';
    
    // dotenv.config({
    //   path: path.resolve(
    //       (process.env.NODE_ENV === 'production') ? './env/.production.env'
    //           :(process.env.NODE_ENV === 'stage') ? './env/.stage.env' : './env/.development.env'
    //   )
    // })
    
    
    async function bootstrap() {
      const app = await NestFactory.create(AppModule);
      // ValidationPipe를 모든 핸들러에 일일이 지정하지 않고, 전역으로 설정하려면 부트스트랩 과정에서 적용 => nest에 이미 validation-pipe가 있기 때문에 직접 만들필요 X
      // app.useGlobalPipes(new ValidationPipe())
    
      // class-transformer를 사용하기 위해서는 transform: true값 지정
      app.useGlobalPipes(new ValidationPipe({
        transform: true
      }))
      await app.listen(3000);
    }
    bootstrap();

     

    6. Class-Transformer 활용.

       => Transform decorator는 transform 함수를 인수로 받고, 여기서 속성의 값과, 그 속성을 가지고 있는 객체를 인수로 받아, 속성 변화하는 함수. 

     

    // NestJS에서 payload 를 처리하기 위해서는 데이터 전송 객체 (DTO)를 구현하면 된다.
    import {IsString, MaxLength, MinLength, IsEmail } from "class-validator";
    import {Expose, Transform} from 'class-transformer'
    
    
    export class CreateUserDto {
        @Transform(params => {
            // name의 받은 인수가 양옆으로 띄어쓰기 되어있는 경우에 줄여주는 transform 
            return params.value.trim()
        })
        @IsString()
        @MinLength(1)
        @MaxLength(20)
        readonly name: string;
    
    
        @IsEmail()
        readonly email: string;
    
        readonly password: string;
    
    }

     

    7. @Transform decorator를 통해 들어오는 param을 변형하여,  조건이 유효하지 않는 경우, throw error를 할 수 있지만, 직접 필요한 검사를 수행하는 decorator를 만들어 사용할 수 있다. 

    import {registerDecorator, ValidationArguments, ValidationOptions} from "class-validator";
    
    const NotIn = (property: string, validationOptions?: ValidationOptions) => { //데커레이터의 인수는 객체에서 참조하려고하는 다른 속성의 이름과 ValidationOptions 를 받는다
        return (object: Object, propertyName: string) => { // registerDecorator를 호출하는 함수 리턴 : 인수로 데커레이터가 선언될 객체와 속성이름 받음
            registerDecorator({ // registerDecorator 함수는 ValidationDecoratorOptions 객체를 인수로 받음
                name: 'NotIn', // Decorator 이름
                target: object.constructor, // Decorator는 객체가 생성될때 적용
                propertyName,
                options: validationOptions, // 유효성 옵션은 Decorator의 인수로 전달받은 것을 사용
                constraints: [property],
                validator: { // 유효성 검사 규칙 (id와 pw가 비슷한 경우, ERROR)
                    validate(value: any, args: ValidationArguments){
                        const [relatedPropertyName] = args.constraints;
                        const relatedValue = (args.object)[relatedPropertyName]
                        // console.log(args.object) => createUserDTO 값을 음
    
                        return typeof value === 'string' && typeof relatedValue === 'string' && !relatedValue.includes(value)
                    }
                }
            })
        }
    }
    
    
    export default NotIn;

     

     

    => create-user-dto.ts 수정

    @Transform(params => {
        return params.value.trim()
    })
    @NotIn('password', {message: 'password 체크'})
    @IsString()
    @MinLength(1)
    @MaxLength(20)
    readonly name: string;

     

     

    1. 
    cmd:: curl -X POST  http://localhost:3000/origin/create -H "Content-Type: application/json" -d '{"name": "name_example  ", "email":"email@example.com","password":"1234"}'
    res:: {"name":"name_example","email":"email@example.com","password":"1234"}%     
    
    2. 
    cmd:: curl -X POST  http://localhost:3000/origin/create -H "Content-Type: application/json" -d '{"name": "name_example  ", "email":"email@example.com","password":"name_example_pw"}'
    res:: {"statusCode":400,"message":["password 체크"],"error":"Bad Request"}%

    '> Backend > NestJS' 카테고리의 다른 글

    230102월_공부일지  (0) 2023.01.02
    20221229목_공부일지  (0) 2022.12.30
    221228수_공부일지  (0) 2022.12.29
    20221227화_공부 일지  (0) 2022.12.28
    20221222Thu_공부 일지  (0) 2022.12.23

    댓글

Designed by Tistory.