안동민 개발노트 아이콘

안동민 개발노트

1장 : NestJS 소개와 기본 개념

첫 번째 NestJS 애플리케이션 만들기

NestJS 개발 환경 구성이 완료되었습니다.

앞서 생성하고 실행해 본 Hello World! 애플리케이션은 NestJS의 가장 기본적인 형태를 보여줬습니다.

이번 절에서는 이 애플리케이션의 기본 구조를 자세히 살펴보고, Hello World! 메시지가 화면에 나타나는 과정, 즉 NestJS가 요청을 처리하는 흐름을 함께 파헤쳐 보겠습니다.


프로젝트 기본 구조 이해하기

NestJS CLI를 통해 nest new 명령어로 프로젝트를 생성하면 다음과 같은 디렉토리 및 파일 구조를 갖게 됩니다. 모든 파일을 다 살펴보기보다는 지금 당장 우리가 이해해야 할 핵심적인 파일들을 중심으로 살펴보겠습니다.

main.ts
app.module.ts
app.controller.ts
app.service.ts
...
.eslintrc.js
.prettierrc
nest-cli.json
package.json
tsconfig.json
...
  • src/ 디렉토리: NestJS 애플리케이션의 모든 소스 코드가 위치하는 핵심 디렉토리입니다. 우리가 작성할 대부분의 코드는 이 안에 들어있습니다.

    • main.ts: 애플리케이션의 진입점(Entry Point) 파일입니다. Node.js 애플리케이션이 실행될 때 가장 먼저 이 파일이 호출됩니다. 마치 건물의 출입구와 같다고 생각하시면 됩니다. 여기서 NestJS 애플리케이션 인스턴스를 생성하고 시작합니다.
    • app.module.ts: 애플리케이션의 루트 모듈(Root Module)입니다. NestJS 애플리케이션의 최상위 모듈이며, 다른 모든 모듈의 기반이 됩니다. 앞서 1장 1절에서 설명했듯이, 모듈은 컨트롤러와 프로바이더를 묶어주는 역할을 합니다.
    • app.controller.ts: AppController를 정의하는 파일입니다. / 경로로 들어오는 HTTP GET 요청을 처리하고 Hello World! 문자열을 반환하는 로직을 담고 있습니다. 클라이언트의 요청을 받아들이는 역할을 합니다.
    • app.service.ts: AppService를 정의하는 파일입니다. 컨트롤러에서 호출하여 실제 비즈니스 로직(여기서는 Hello World! 문자열을 반환하는 간단한 로직)을 처리하는 프로바이더의 한 종류입니다.
  • package.json: 프로젝트의 메타데이터와 의존성 패키지 목록, 실행 스크립트 등을 정의하는 파일입니다. npm install 명령어를 실행하면 여기에 명시된 패키지들이 설치됩니다.

  • tsconfig.json: TypeScript 컴파일러 옵션을 설정하는 파일입니다. NestJS는 TypeScript 기반이므로, 이 파일이 중요하게 사용됩니다.


NestJS 애플리케이션의 실행 흐름 추적하기

이제 http://localhost:3000으로 접속했을 때 Hello World!가 어떻게 출력되는지, NestJS 내부의 동작 흐름을 따라가 보겠습니다.

main.ts 실행: 애플리케이션을 시작하면 npm run start:dev 명령어가 main.ts 파일을 실행합니다. 이 파일의 핵심 코드는 다음과 같습니다.

src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  // NestJS 애플리케이션 인스턴스를 생성합니다.
  // 여기서 AppModule은 애플리케이션의 루트 모듈입니다.
  const app = await NestFactory.create(AppModule);
  // 애플리케이션이 3000번 포트에서 수신 대기하도록 합니다.
  await app.listen(3000);
}
bootstrap(); // bootstrap 함수를 호출하여 애플리케이션을 시작합니다.

NestFactory.create(AppModule)를 통해 AppModule을 기반으로 NestJS 애플리케이션이 초기화되고, app.listen(3000)을 통해 3000번 포트에서 들어오는 HTTP 요청을 수신 대기하게 됩니다.

AppModule 초기화: main.ts에서 AppModule을 전달했으므로, NestJS는 app.module.ts 파일을 읽고 초기화합니다.

src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
  imports: [], // 다른 모듈을 가져올 때 사용합니다.
  controllers: [AppController], // 이 모듈에 속한 컨트롤러를 등록합니다.
  providers: [AppService], // 이 모듈에 속한 프로바이더를 등록합니다.
})
export class AppModule {}

@Module() 데코레이터 안의 controllers 배열에 AppController가 등록되어 있고, providers 배열에 AppService가 등록되어 있는 것을 볼 수 있습니다. NestJS는 이 정보를 바탕으로 AppControllerAppService를 인스턴스화하고, 의존성 주입을 준비합니다.

요청 처리: AppController 동작: 이제 http://localhost:3000으로 웹 브라우저가 요청을 보냅니다. 이 요청은 NestJS 애플리케이션에 도달하고, 등록된 컨트롤러 중 해당 요청을 처리할 컨트롤러를 찾습니다.

src/app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller() // 이 클래스가 컨트롤러임을 나타냅니다. 기본 경로는 '/' 입니다.
export class AppController {
  // AppService를 주입받습니다. NestJS가 자동으로 AppService 인스턴스를 제공합니다.
  constructor(private readonly appService: AppService) {}

  @Get() // HTTP GET 요청, 경로가 '/'일 때 이 메서드가 실행됩니다.
  getHello(): string {
    // AppService의 getHello() 메서드를 호출하여 결과값을 반환합니다.
    return this.appService.getHello();
  }
}

@Controller() 데코레이터는 이 클래스가 컨트롤러임을 선언하며, 기본 경로가 /임을 의미합니다. @Get() 데코레이터는 이 메서드가 HTTP GET 요청을 처리하며, 경로가 컨트롤러의 기본 경로와 같을 때(즉, /) 호출됨을 나타냅니다.

AppController 생성자를 보면 private readonly appService: AppService 코드가 있습니다.

이것이 NestJS 핵심 기능 중 하나인 의존성 주입(Dependency Injection)입니다.

NestJS는 AppService 인스턴스를 자동 생성해 AppController에 주입하므로, new AppService()처럼 직접 인스턴스를 만들 필요가 없습니다.

getHello() 메서드는 주입받은 appServicegetHello() 메서드를 호출합니다.

비즈니스 로직 처리: AppService 동작: AppController의 요청을 받은 AppService가 실행됩니다.

src/app.service.ts
import { Injectable } from '@nestjs/common';

@Injectable() // 이 클래스가 프로바이더임을 나타냅니다. (다른 곳에 주입될 수 있음)
export class AppService {
  getHello(): string {
    return 'Hello World!'; // 간단하게 "Hello World!" 문자열을 반환합니다.
  }
}

@Injectable() 데코레이터는 이 클래스가 NestJS의 의존성 주입 시스템에 의해 관리되는 프로바이더임을 나타냅니다. getHello() 메서드는 단순히 'Hello World!' 문자열을 반환합니다.

응답 반환: AppService.getHello()가 반환한 'Hello World!' 문자열은 AppController.getHello()로 돌아오고, 최종적으로 클라이언트(웹 브라우저)에게 HTTP 응답으로 전달되어 화면에 표시됩니다.

아래 다이어그램은 Hello World 흐름 안에서 Nest의 DI 컨테이너가 컨트롤러와 서비스를 어떻게 연결하는지 정리합니다.


이번 절에서는 NestJS가 모듈, 컨트롤러, 프로바이더를 이용해 요청을 받고 응답을 반환하는 흐름을 정리했습니다. 이후 기능도 이 기본 구조 위에서 라우팅, 의존성, 서비스 로직을 확장하는 방식으로 구성됩니다.

다음 절에서는 NestJS의 핵심 개념인 의존성 주입(Dependency Injection)에 대해 좀 더 자세히 알아보겠습니다.

첫 애플리케이션은 파일 이름을 외우기보다 요청이 어떤 객체를 차례로 지나 응답이 되는지 따라가면 구조가 선명해집니다.