요청 데이터의 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 설정하기
- global validation 사용하기 ( 위에서 방금 했음)
- 요청의 body에 있어야 할 다른 속성을 구분하는 클래스 생성하기
- 클래스에 validation rules 추가하기
- 클래스를 요청 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단계
- class-transformer를 사용하여 body를 DTO 클래스의 인스턴스로 바꾼다.
- class-validator 로 instance를 검사한다.
- 에러가 없으면 요청 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로 넘겨준다.