최근 희정님과 함께 하는 TypeScript 스터디는 nomad의 blockchain 인강을 바탕으로 하고 있었다. 그런데 그 인강을 다 들으면서 다른 학습 자료들을 찾아보았다. TypeScript로 하는 nestJS 강의가 있어서 보기 시작했다. 거의 9개월 만에 백엔드와 관련된 공부를 해보는 거라 너무 기대됐고 바로 하기로 했다. 아직 강의의 초반이기도 하고 nestJS에 대한 간단한 소개를 해주는 강의이기 때문에 굉장히 심도있는 내용은 아니겠지만 그래도 배우는 내용을 정리하는데 의의를 두고 정리를 해보도록 하겠다.
유튜브를 예를 들면 video Module이 있을 수 있고, user Module이 있을 수 있고, comment Module이 있을 수 있다. 즉 Application의 일부가 Module로, 한 가지 역할을 하는 부분이라고 이해하면 된다.
이런 모듈은 한 곳에서 모인다. nestJS는 main.ts가 무조건 있는데, 이건 정해져 있는 부분이다. 이 안에는 AppModule이라는 모듈이 들어간다. 이것은 Root Module인데 module.ts에 있는 것이다. 모든 모듈은 여기에 import되기 때문에 Root Module이라고 하는 것이다.
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
// app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
// 비디오와 관련된 Module이 AppModule에 import에 있다.
@Module({
imports: [],
controllers: [videoController], // app.controller.ts
providers: [videoService], // app.service.ts
})
export class AppModule {}
이노베이션 캠프 초반에 flask로 백엔드 빌드를 한 적이 있다. 그 당시에 데코레이터와 컨트롤러를 붙여서 쓰는 것을 보고는 express와 다르게 굉장히 정형화 되어 있다는 느낌을 받았다. 그런데 그런 느낌을 express기반으로 만들어 진 nest에서 받았다. 똑같이 쓰기 때문이다.
// app.controller.ts
import { Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get('/hello')
sayHello(): string {
return this.appService.getHi()
}
}
nestJS에는 데코레이터라는 것이 있다. @Get() 처럼 @로 시작하는 것이 데코레이터이다. 예시에 있는 @Get('/hello')은 '/hello'라는 url에 들어가는 get 요청과 아래에 있는 sayHello라는 컨트롤러와 맵핑해준다. 즉 express에서의 get 라우터 역할을 해주는 것이다. 단 컨트롤러와 붙여써야 한다. 이런 부분에서 자유롭게 컨트롤러를 써도 되는 express와 형식적으로 차이가 느껴졌다. 다 쓰고 보니 훨씬 깔끔한 느낌이 들었다.
데코레이터에는 Get, Post와 같은 것만 있는 것이 아니다. 그래서 '아, express에서 get을 @Get으로 쓰는구나'정도로 이해하기 보단 하나의 함수라고 이해하는 것이 맞는 것 같다. 그래서 니코도 '클래스 위의 함수이고 클래스를 위해 움직인다고 이해하면 된다.'라고 말을 했다.
/**
* Route handler (method) Decorator. Routes HTTP GET requests to the specified path.
*
* @see [Routing](https://docs.nestjs.com/controllers#routing)
*
* @publicApi
*/
export declare const Get: (path?: string | string[]) => MethodDecorator;
실제로 @Get을 살펴보면 함수라는 것을 알 수 있다.
// @app.controller.ts
@Get("/hello")
sayHello(): string {
return "Hello Everyone"; // 브라우저에 Hello Everyone가 출력된다.
}
컨트롤러 파일에서 위처럼 코드를 적어도 브라우저에는 Hello Everyone이 잘 출력된다. 하지만 nestJS에서는 이렇게 쓰지 않고 아래처럼 service파일에 컨트롤러를 나눠서 적는다.
// @app.controller.ts
@Get("/hello")
getHello(): string {
return this.appService.getHello();
}
// @app.service.ts
@Injectable()
export class AppService {
getHello(): string {
return 'Hello Nest!';
}
}
// 실제적인 함수는 service에 위치하고 있다.
// getHello라고 controller에 이름을 지었으면 service에서도 동일한 이름으로 만드는 것이 관례긴 하다.
nestJS는 컨트롤러와 비즈니스 로직을 분리하길 원한다. 그래서 컨트롤러 파일에서는 url과 컨트롤러를 맵핑해주고, 컨트롤러가 호출하는 메소드는 서비스 파일에 정의되어 있는 형태이다. 이렇게 분리를 하기 때문에 훨씬 코드가 깔끔하고 생산성이 더 높을 수 있다는 생각을 했다.
일단 굉장히 깔끔한 프레임워크라는 생각을 했다. 비지니스 로직이 완전히 분리된 것, 그리고 데코레이터와 컨트롤러가 같이 있는 점 등 훨씬 생산성이 높다는 생각이 들었다. 또 nestJS는 express로 빌드할 때 필요한 여러 기능들이 프레임워크에 내장되어 있다는 이야길 많이 들었다. 이런 이야길 들었기 때문에 다음에 배울 DTO부분과 validationPipe부분이 기대가 된다. express에서는 validation과정을 일일이 해줘야 했는데 nestJS에서는 다른 좋은 방법이 있는 것 같기 때문이다.