매일 배운 것을 정리하며 기록합니다. NestJS를 통해 Rest API 만들어 보았습니다.
Body를 검사하는데 NestJS에는 아주 편리한 기능이 있습니다.
먼저 DTO(Data Transfer Object)를 만들 겁니다.
src/movies/dto/create-movie.dto.ts 파일을 만듭니다.
dto파일에는 movie를 만들기 위해 필요한 것들을 나열합니다.
movie.entity.ts를 보시면 id, title, year, genres가 있습니다.
유저가 영화를 등록할 때 id는 넣으면 안되니 id를 제외한 목록들을 dto파일에 나열합니다.
dto를 다 만들면 컨트롤러와 서비스에도 수정을 합니다.
create함수의 movieData 파라미터의 타입을 지정해 줍니다.
이것만으로는 유효성 검사가 되지 않습니다.
아직 잘못된 Body를 받아도 그대로 받아들이기 떄문에 추가 작업들이 필요합니다.
main.ts에서 유효성 검사용 파이프 작업을 진행합니다.
파이프는 미들웨어라고 생각할 수 있습니다.
이를 위해 먼저 패키지를 설치합니다.
$ npm i class-validator class-transformer
패키지 설치 후 dto 코드를 수정한 뒤 테스트를 해 보겠습니다.
이와 같이 타입스크립트로 코드의 유효성을 체크할 수 있습니다.
이제는 hack과 같은 코드는 보낼 수 없습니다.
이는 ValidationPipe와 그걸로 검사하는 CreateMovieDto를 사용하고 있기 때문입니다.
ValidationPipe는 아주 멋있는 옵션들을 가지고 있는데, 그 중 whitelist를 사용할 겁니다.
whitelist는 true 설정 시 아무 데코레이터가 없는 프로퍼티의 object는 걸러줍니다.
저희의 프로퍼티에는 title, year, genres만 데코레이터가 있으니 이 프로퍼티들 이외에는 다 걸러주기에 hack같은 프로퍼티를 가진 object는 걸러줍니다.
그리고 누군가 이상한 리퀘스트를 보내면 요청 자체를 막아 보안을 업그레이드 할 수 있습니다.
forbidNonWhitelisted속성을 true로 해주면 됩니다.
"property hack should not exist" 에러 메시지를 출력합니다.
유효성 검사를 마쳤으니 class-transformer 패키지를 사용해 보겠습니다.
현재 문제가 되는 부분은 컨트롤러의 movieId를 string으로 받는다는 점입니다.
url은 string이기 때문에 어쩔 수 없이 string으로 받은 후 서비스에서 '+'를 붙여 숫자로 변경하고 있습니다.
컨트롤러에서 movieId를 string으로 받음
서비스에서 id를 넘버로 바꿔줌
이러한 문제를 파이프에서 transform을 이용해서 타입을 변경해 보겠습니다.
transform을 true로 설정합니다.
transform을 true로 설정해 주면 컨트롤러, 서비스에서 모두 movieId에 원하는 타입을 작성하면 됩니다.
NestJS는 타입을 받아서 넘겨줄 때 자동으로 타입을 변환해 줍니다!
이 놀라운 기능은 express를 이용한다면 전혀 도움받을 수 없지만 NestJS라는 프레임워크를 이용하기 때문에 가능한 기능입니다.
createDTO처럼 updateDTO도 같은 원리로 만들어 사용하면 됩니다.
update의 경우 모든 항목을 업데이트 할 수도 있지만 특정 항목만 업데이트 할 수도 있습니다.
그렇기 떄문에 update클래스는 선택 속성으로 만들어 보겠습니다.
update-movie.dto.ts 생성
dto를 생성했으면 컨트롤러와 서비스의 updateData에 타입으로 지정해 줍니다.
위의 코드처럼 선택 속성을 만들 수 있지만 NestJS의 기능을 사용하면 무슨 차이가 있는지 살펴보겠습니다.
먼저 mapped-types를 설치합니다.
$ npm i @nestjs/mapped-types
mapped-types는 타입을 변환시키고 사용할 수 있게 해 주는 패키지입니다.
해당 패키지에서 PartialType을 구조 분해하여 사용합니다.
그리고 CreateMovieDto를 상속받습니다.
이 한 줄로 위의 중복의 코드를 정리할 수 있습니다.
이번엔 app.module을 조금 더 좋은 구조로 만들어 볼 겁니다.
사실 controller와 provider의 현재 모습과 달리 app.module은 AppController, AppProvider만 가지고 있어야 합니다.
그래서 MoviesController, MoviesService를 movies.module로 옮길 겁니다.
NestJS에서 앱은 여러 개의 모듈로 구성이 됩니다.
현재 app.module의 코드
$ nest g(generate) mo(module)
? What name would you like to use for the module? movies CREATE src/movies/movies.module.ts (83 bytes) UPDATE src/app.module.ts (348 bytes)
모듈을 생성하니 파일을 만들고 app.module을 업데이트 했습니다.
기존 app.module에 있던 controller, provider는 movie.module.ts로 이동 시킵니다.
app.module에 controller, provider가 비었으니 채우기 위해 또 한 번 파일을 만듭니다.
$ nest g co
controller를 만들면 app 디렉토리에서 뺴내고 디렉토리와 spec파일은 삭제해 줍니다.
다음과 같은 경로로 만듭니다.
app.controller 파일을 간단히 작성하여 테스틀 진행해 봅니다.
이것이 NestJS가 앱의 구조를 짜는 방법입니다.
코드를 통해 하나씩 살펴 보겠습니다.
app.modules.ts
app.module.ts에서 @Module의 imports에 여러가지를 넣고 NestJS가 작동하면 모든 걸 하나의 모듈로 생성해 줍니다.
app.module.ts의 import에 있는 것들이 6번 줄을 통해 하나의 모듈, app으로 만들어 집니다.
NestJS에서 알아둬야 할 것은 Dependency Injection이라는 개념입니다.
movies.controller.ts
무비 컨트롤러에서 getAll을 보시면 this.moviesService.getAll()을 사용하고 있습니다.
이 코드가 가능한 것은 18번에서 moviesService 프로퍼티를 만들고 MoviesService 타입을 지정해 줬기 때문입니다.
타입스크립트가 아니였다면 18번의 코드로만은 작동하지 않았을 겁니다.
무언가를 import 해 줘야 하는데 타입스크립트를 사용하여 단지 타입만 import하고 있습니다.
이는 타입스크립트의 장점을 보여주는 대목입니다.
movies.module.ts
movies.module을 보면 controller와 provider를 import하고 있습니다.
이 provider가 []안에 있는 모든 것들을 import 하기 때문에 위에서 타입을 추가하는 것만으로도 작동이 되는 겁니다.
이걸 dependency injection이라고 합니다.
NestJS가 MovieService를 import하고 controller에 inject합니다.
그래서 movies.service.ts에 @Injectable()이 있던 겁니다.
만약 movies.module.ts에 provider가 없다면 어떻게 될까요?
Error: Nest can't resolve dependencies of the MoviesController (?). Please make sure that the argument MoviesService at index [0] is available in the MoviesModule context.
MoviesController가 MoviesService가 필요하다고 말합니다.
컨트롤러 18번 줄에서 사용하기 때문입니다.
그래서 provider가 필요하고 이걸 dependency injection이라고 합니다.
provider를 작성함으로 NestJS가 import를 대신 하게끔 합니다.
NestJS는 Express위에서 돌아갑니다.
그래서 request, response 객체가 필요하면 사용할 수 있습니다.
23번 @Req() req, @Res res를 통해 request, response 객체 사용
그러나 request, response와 같은 express 객체를 직접적으로 사용하는 것은 좋지 않습니다.
왜냐하면 NestJS는 두 개의 프레임워크랑 작동하는데 기본적으로 express위에서 동작하지만 fastify로 전환할 수 있기 때문입니다.
fastify도 express처럼 동작하나 2배 정도 빠르다고 합니다.
중요한 것은 NestJS 프레임워크 위에 2개의 라이브러리가 동시에 돌아간다는 것입니다.
그래서 직접적인 express 객체 사용을 지양합니다.
이번 파트에서는 이러한 방법이 있다는 것만 알아두면 될 거 같습니다.
Reference : 노마드 코더, 『NestJS로 API 만들기』, #2.4~#2.7