[Nest.js V9] 정리 노트 - 1

Nest.js 기본 개념

Intro


I. 프로젝트 설정하기


  • 우선 프로젝트 워크스페이스를 생성한 후 package.json종속성 파일을 추가하기 위해 터미널에 다음과 같이 입력합니다.
bash
npm init -y

이후 다음과 같이 5개의 라비르러리 설치를 진행합니다. 버전은 강의의 경우 7버전이였지만 9버전으로 진행하겠습니다.

bash
npm install @nestjs/common @nestjs/core @nestjs/platform-express reflect-metadata@0.1.13 typescript
json
// package.json
{
  "name": "scratch",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@nestjs/common": "^9.4.1",
    "@nestjs/core": "^9.4.1",
    "@nestjs/platform-express": "^9.4.1",
    "reflect-metadata": "^0.1.13",
    "typescript": "^5.0.4"
  }
}

그럼 추가된 모듈이 어떤 역할을 하는지 알아봅시다.

[1] @nsetjs/common
Nest 라이브러리는 여러 패키지로 나뉩니다. 이 모듈은 Nest애플리케이션을 구축하는 데 사용할 사용할 함수, 클래스 및 기타 항목의 대부분이 이 공통 모듈에서 지원됩니다.

[2] @nsetjs/platform-express 내부적으로 Nest는 들어오는 요청(requset)를 처리하지 않습니다. 요청을 처리하기 위해 외부 구현에 의존합니다. HTTP 구현을 연결해야 하는 부분에 이 모듈을 사용하면 들어오는 요청을 처리할 API가 여기에 있다고 말하는 일종의 서버를 제공하게 됩니다. 그리고 응답(response)를 보내주게 됩니다. Nest 애플리케이션에서 현재 HTTP 서버 구현을 위한 두 가지 옵션이 존재합니다. 바로 Express, Fast Wi-Fi(Fastify)입니다. 현재는 Express를 사용할 것입니다. Express와 Nest 사이 일종의 어뎁터를 사용하여 작동하게 됩니다. 이제 Nest는 들어오는 모든 HTTP 요청을 처리하기 위해 Express를 사용하게 됩니다.

[3] reflect-metadata 데코레이터와 연결된 라이브러리

[4] Typescript 현재 Nest 애플리케이션은 Typescript와 함께 사용하는곳이 대부분입니다. Typescript를 사용해 구현합니다.

II. Typescript 컴파일러 설정하기


프로젝트 워크스페이스에 json 파일을 추가합니다. tsconfig.json를 생성하고 다음과 같이 입력합니다. Nest 애플리케이션에 적용할 중요 옵션인 experimentalDecorators, emitDecoratorMetadata는 이후 설명하겠습니다.

json
// tsconfig.json
{
  "compilerOptions": {
    "module": "CommonJS",
    "target": "es2017",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
  }
}

III. Nest 모듈, 컨트롤러 만들기


우선 일련의 서버는 서버는 들어온 요청에 대해 다음과 같은 수순으로 처리합니다.

  • 요청을 수신하고 데이터에 유효성 검증
  • 사용자가 인증되었거나 권한이 있는지 확인
  • 특정 기능으로 라우팅
  • 비즈니스 로직을 실행하여 데이터베이스에 접근

여기에서 Nest 에서는 아래와 같은 도구들을 제공합니다.

Pipe는 들어오는 요청 데이터의 유효성을 검사할 수 있습니다.
Guard는 사용자가 요청이 가능한 사용자인지에 대한 인증, 권한이 있는지 확인합니다.
Controller는 라우팅 기능이 포함됩니다.
Service는 요청에 따른 작업을 어떻게 수행할 지에 대한 비즈니스 로직이 포함됩니다.
Repository는 데이터베이스에 접근합니다.

이외에도 다음과 같은 도구들이 제공됩니다. 도구들은 프로젝트를 생성하면서 알아보도록 합시다. Modules, Filters, Interceptors

우선 위의 도구 중 ControllerModules을 구성해봅시다. 프로젝트 워크 스페이스에서 src폴더를 생성합니다. 그리고 안에 main.ts파일을 생성합니다.

ts
import { Controller, Module, Get } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";

// @Controller는 데코레이터입니다. 이 클래스는 컨트롤러를 담당한다고 명시합니다.
// 이 클래스는 요청을 처리하고 라우팅을 진행합니다.
@Controller()
class AppController {

  // 누군가 요청(GET) 한다면 이 메서드로 라우팅 합니다. Get 모듈을 import 해줍니다.
  @Get()
  getRootRoute() {
    return 'hi there!';
  }
}

// module 데코레이터는 구성 옵션이나 개체를 인자로 전달해야합니다.
// 이제 애플리케이션이 시작될 때 이 AppModule 클래스를 불러올 것입니다.
@Module({
  // 컨트롤러를 설정하면 자동으로 인스턴스화가 진행되어 인스턴스를 생성합니다.
  // 인자로 객체를 주고 controllers키에 인스턴스로 생성할 클래스들을 명시해줍니다. 지금은 AppController 클래스로 명시해줍니다.
  // AppController의 인스턴스가 생성됩니다.
  controllers: [AppController]
})
class AppModule {

}

// 비동기로 작동할 함수를 생성하고 실행합니다.
async function bootstrap() {
  // AppModule 을 인스턴스화 하기 위해 NestFactory.create 함수에 인자로 입력합니다.
  const app = await NestFactory.create(AppModule);
  
  // 인스턴스를 생성한 다음 들러오는 요청을 3000번 포트로 수신하도록 지시합니다.
  await app.listen(3000);
  // await을 사용했기 때문에 app.listen()함수 수행이 끝나면 다음 구문이 실행됩니다.
}

bootstrap().then(()=> {
  // server end
});

간단한 서버 코드를 작성했습니다.
이제 서버를 실행하여 결과를 살펴봅시다. 터미널을 실행한 뒤 다음과 같이 입력합니다.

bash
npx ts-node-dev src/main.ts

정상적으로 실행됐다면 다음과 같이 로그가 표시됩니다. 만일 오류가 발생한다면 로그를 확인해보도록 합시다.

log
Need to install the following packages:
  ts-node-dev@2.0.0
Ok to proceed? (y) y
[INFO] 09:44:52 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.1, typescript ver. 4.9.5)
[Nest] 6066   - 2023. 01. 31. 오전 9:44:52   [NestFactory] Starting Nest application...
[Nest] 6066   - 2023. 01. 31. 오전 9:44:52   [InstanceLoader] AppModule dependencies initialized +33ms
[Nest] 6066   - 2023. 01. 31. 오전 9:44:52   [RoutesResolver] AppController {}: +2ms
[Nest] 6066   - 2023. 01. 31. 오전 9:44:52   [RouterExplorer] Mapped {, GET} route +1ms
[Nest] 6066   - 2023. 01. 31. 오전 9:44:52   [NestApplication] Nest application successfully started +1ms

이제 브라우저를 실행하고 3000포트로 GET요청을 해봅시다.

요청에 따른 응답이 정상적으로 넘어온 것을 확인할 수 있습니다. 지금 상태에서 정상적으로 동작하지만, 프로젝트 관리를 위해 컨트롤러 클래스와 모듈을 별도의 파일로 구분하여 작업해보도록 합시다.
그 전에 우선 파일 네이밍 컨벤션부터 알아보도록 합시다.

위 코드를 보면 bootstrap 함수로 AppModule클래스를 인스턴스화 합니다. AppModule은 또한 Controller클래스를 인스턴스화 하여 내재하고 있구요. 지금은 하나에 파일에 합쳤지만 이를 구분한다면 다음과 같을겁니다.

  • bootstrap > main.ts
  • AppModule > app.module.ts
  • Controller > app.controller.ts

정리하자면 다음과 같게 됩니다.

ts
// app.controller.ts
import { Controller, Get } from "@nestjs/common";

// @Controller는 데코레이터입니다. 이 클래스는 컨트롤러를 담당한다고 명시합니다.
// 이 클래스는 요청을 처리하고 라우팅을 진행합니다.
@Controller()
export class AppController {

  // 누군가 요청(GET) 한다면 이 메서드로 라우팅 합니다. Get 모듈을 import 해줍니다.
  @Get()
  getRootRoute() {
    return 'hi there!';
  }
}
ts
// app.module.ts
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'

// module 데코레이터는 구성 옵션이나 개체를 인자로 전달해야합니다.
// 이제 애플리케이션이 시작될 때 이 AppModule 클래스를 불러올 것입니다.
@Module({
  // 컨트롤러를 설정하면 자동으로 인스턴스화가 진행되어 인스턴스를 생성합니다.
  // 인자로 객체를 주고 controllers키에 인스턴스로 생성할 클래스들을 명시해줍니다. 지금은 AppController 클래스로 명시해줍니다.
  // AppController의 인스턴스가 생성됩니다.
  controllers: [AppController]
})
export class AppModule {}
ts
// main.ts
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";

// 비동기로 작동할 함수를 생성하고 실행합니다.
async function bootstrap() {
  // AppModule 을 인스턴스화 하기 위해 NestFactory.create 함수에 인자로 입력합니다.
  const app = await NestFactory.create(AppModule);
  
  // 인스턴스를 생성한 다음 들러오는 요청을 3000번 포트로 수신하도록 지시합니다.
  await app.listen(3000);
  // await을 사용했기 때문에 app.listen()함수 수행이 끝나면 다음 구문이 실행됩니다.
}

bootstrap().then(()=> {
  // server end
});

이제 url 을 잠깐 조정해봅시다 @Controller, @Get은 인자로 스트링 문자열을 받습니다. 이를 이용해 url 을 조정할 수 있죠.

ts
// app.controller.ts
import { Controller, Get } from "@nestjs/common";

// @Controller는 데코레이터입니다. 이 클래스는 컨트롤러를 담당한다고 명시합니다.
// 이 클래스는 요청을 처리하고 라우팅을 진행합니다.
@Controller('/app')
export class AppController {

  // 누군가 요청(GET) 한다면 이 메서드로 라우팅 합니다. Get 모듈을 import 해줍니다.
  @Get('/hi')
  getRootRoute() {
    return 'hi there!';
  }

  @Get('/exit')
  getByeThere() {
    return 'byt there!';
  }
}