icon
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 응답으로 전달되어 화면에 표시됩니다.


이러한 과정을 통해 NestJS가 어떻게 모듈, 컨트롤러, 프로바이더를 활용하여 클라이언트의 요청을 받아들이고 처리한 후 응답을 반환하는지 이해하셨기를 바랍니다. 이는 NestJS 애플리케이션의 가장 기본적인 동작 방식이며, 앞으로 우리가 만들 모든 복잡한 기능들도 결국 이 기본 틀 안에서 동작하게 됩니다.

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