요청 데이터의 Validation

Data Transfer Object

요청 데이터의 Validation(타당성) 검사

Decorator로 요청 데이터 접근하기

HTTP 요청

3가지로 구성

  • Start line : POST /messages/5?validate=true HTTP/1.1
  • Headers : Host: localhost:3000, Content-Type: application/json
  • Body : {"content": "hi there"}

Nest의 경우 decorator 이용

POST /messages/5?validate=true HTTP/1.1

  • 5 : @Param(‘id’)
  • validate=true : @Query()

Headers는 @Headers()

Body 는 @Body()

위와 같이 decorator를 이용하면 된다.

messages.controller.ts 의 컨트롤러를 다시 작성해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// messages.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';

@Controller('messages') // class decorator
export class MessagesController {
  @Get() // method decorator
  listMessages() {}
  
  @Post()
  createMessage(@Body() body: any) { // @Body, @Param : argument decorator
    console.log(body);
  }

  @Get('/:id')
  getMessage(@Param('id') id: string) {
    console.log(id);
  }
}

이제 API Client로 확인해보자.

requests.http 파일을 다음과 같이 작성한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
### List all messages
GET http://localhost:3000/messages


### Create a new message
POST http://localhost:3000/messages
content-type: application/json

{
    "content": "hi there"
}


### Get a particular message
GET http://localhost:3000/messages/123123123

두번째 POST 요청을 sent한 후 터미널을 확인해보면

{ content: ‘hi there’ }가 나타난 것을 확인할 수 있다.

세번째도 마찬가지.

API는 현재 데이터가 없으니 당연히 받는게 없다.

Validation를 위한 파이프 사용

두가지 라이브러리를 설치하자.

npm install class-validator

npm install class-transformer

ValidationPipe : Nest에 내장된 Pipe

하나의 루트 핸들러에 유효성 검사해준다.

main.ts 파일에 추가해준다.

1
2
3
4
5
6
7
8
9
10
11
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { MessagesModule } from './messages/messages.module';

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

Automatic Validation 설정하기

  1. global validation 사용하기 ( 위에서 방금 했음)
  2. 요청의 body에 있어야 할 다른 속성을 구분하는 클래스 생성하기
  3. 클래스에 validation rules 추가하기
  4. 클래스를 요청 handler에 적용하기

2단계

DTO(Data Transfer Object) : 만들어야할 클래스. 데이터 전송 객체

src/messages 안에 dtos 라는 폴더를 만들고 그 내 부에 create-message-dto.ts 를 만든다.

다음과 같이 export 할 수 있는 클래스를 만든다.

1
2
3
export class CreateMessageDto {
    content: string;
}

3단계

decorator를 import하여 추가한다.

1
2
3
4
5
6
import { IsString } from 'class-validator';

export class CreateMessageDto {
    @IsString()
	content: string;
}

4단계

컨트롤러에 적용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// messages.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CreateMessageDto } from './dtos/create-message.dto';

@Controller('messages')
export class MessagesController {
  @Get()
  listMessages() {}

  @Post()
  createMessage(@Body() body: CreateMessageDto) { // 적용 위치
    console.log(body);
  }

  @Get('/:id')
  getMessage(@Param('id') id: string) {
    console.log(id);
  }
}

확인하기

requests.http 에서 POST 요청을 보내면

HTTP/1.1 201 Created X-Powered-By: Express Date: Thu, 05 Oct 2023 07:35:36 GMT Connection: close Content-Length: 0

라고 떴다. 내용이 없어서 그렇지 오류는 안뜨는 상태.

우린 데이터 컨텐츠가 string인지 확인하는 코드를 추가했으므로

“hi there”가 아니라 123 이라는 number를 보내보자. 큰따옴표 없애야 number 취급되는 것 잊지말자.

HTTP/1.1 400 Bad Request X-Powered-By: Express Content-Type: application/json; charset=utf-8 Content-Length: 79 ETag: W/”4f-JeosnzYSS4di/3R1CZ9l58bJ9D0” Date: Thu, 05 Oct 2023 07:36:46 GMT Connection: close { “message”: [ “content must be a string” ], “error”: “Bad Request”, “statusCode”: 400 }

라고 뜨며 validation 과정에서 문제가 생겼음을 알 수 있다.

위 과정들을 좀 더 자세하게 알아보

Validation 자세히 알아보기

DTO(Data Transfer Object)

두 장소 간의 데이터나 정보 전달. 데이터가 어떤 type인지 알 수 있다.

class-transformer

일반 JSON 객체를 클래스의 인스턴스로 변환하는 패키지 (README)

1
2
3
4
5
6
7
8
9
[
    {
        "id": 1,
        "firstName": "Johny",
        "lastName": "Cage",
        "age": 27
    },
    ...
]

와 같은 형태를

1
2
3
4
5
6
export class User {
    od: number;
    firstName: string;
    lastName: string;
    age: number;
}

로 바꿔준다. 알맞은 메서드도 추가해준다.

class-validator

내부의 validation decorator는 값의 type 등을 검증해주는 decorator가 많다. (README)

@IsDate(), @IsBoolean() 등등

Validation Pipe의 3단계

  1. class-transformer를 사용하여 body를 DTO 클래스의 인스턴스로 바꾼다.
  2. class-validator 로 instance를 검사한다.
  3. 에러가 없으면 요청 handler에 body를 넘긴다.

의문점

Typescript는 컴파일될 때 Javascript 언어로 변환되어 실행된다. TS언어를 실행하는 엔진은 없다.

createMessage 메서드에서 body의 type은 CreateMessageDto 였다.

Typescript가 컴파일되면 Javascript로 변환되면서 type 보존이 안된다.

즉 실행할 당시에는 CreateMessageDto가 없는 type일텐데 어떻게 가능한걸까?

  • Typescript 에서 (변환 전)
    • addMessage(@Body() body: AddMessageDto) {}
  • Javascript 에서 (변환 후)
    • addMessage(body) {}

TS가 JS로 변환되면서 코드 내 모든 type 정보가 제거된다?

tsconfig.json에 "emitDecoratorMetadata": true일 땐 아니다. 이 옵션은 type 정보를 Javascript로 넘겨준다.