NestJS 설치 및 기본 구조 분석 + nest cli

세하·2025년 10월 16일

NestJS

목록 보기
1/8
post-thumbnail

Nest.js는 Node.js 런타임 위에서 동작하는 TypeScript용 오픈 소스 백엔드 웹 프레임워크이다.
설치 시 따로 설정을 통해 JavaScript로도 사용 가능하다.

Nest.js를 보면 Java의 Spring처럼 기본적인 틀이 있고 정해진 규칙이 있어 뭔가 익숙함을 느끼게 된다. Nest.js는 Node.js를 사용하며 기본적인 규칙과 제약을 공유함으로써 다양한 프로젝트를 진행하더라도 생산성의 향상과 유지보수의 증가로 이어진다.

NestJS 설치

https://docs.nestjs.com/
NestJS CLI를 컴퓨터 시스템에 전역적으로 설치한다. NestJS CLI는 우리의 NestJS 애플리케이션을 생성하고 관리하는데 사용할 수 있는 명령어 세트를 제공한다.

npm install -g @nestjs/cli

아래의 명령어를 통해 잘 설치되었는지 확인

nest --version

NestJS CLI는 다양한 명령어 세트를 제공하는데 아래의 명렁어를 통해 확인할 수 있다.

nest

개발을 진행할 때도 추가해야 하는 기능이 있다면 직접 폴더와 파일을 만드는 것이 아니라, CLI 를 사용하면 자동으로 구조를 갖춘 파일들을 생성해주기 때문에 CLI를 사용하는 것을 추천한다.

예를 들어 아래의 명령어들을 입력하면

## 유저 모듈 생성
$ nest g mo users

## 유저 컨트롤러 생성
$ nest g co users

## 유저 서비스 생성
$ nest g s users

src > users 폴더가 추가되며 모듈, 컨트롤러, 서비스가 추가된다.
모듈이 자체적으로 연결되어 있다.

NestJS 프로젝트 생성 및 실행

프로젝트를 만들고 싶은 폴더로 이동하여 아래 명령어를 실행한다

nest new new-project

new-project 부분에는 본인이 정한 프로젝트 명을 알아서 넣으면 된다.
* 새로운 NestJS 프로젝트를 현재 위치의 new-project라는 이름의 폴더 안에 생성할 것임

생성한 프로젝트로 이동하여

npm run start:dev

를 실행하고 ocalhost:3000로 접속해보면 "Hello, world!"를 확인할 수 있다.

:dev: 개발(development) 모드로 애플리케이션을 시작한다.
서버를 시작할 때 nodemon 스크립트가 사용된다.
nodemon: 코드의 변화를 감지하고 있다가 우리가 코드가 수정되었을 때, 이를 감지하여 자동으로 서버를 재시작하여 수정사항이 서버에 반영되도록 하는 기능

기본 구조

처음 프로젝트를 생성하면 아래와 같은 구조로 이루어져있다.

프로젝트에서 제일 중요한 폴더는 아무래도 TypeScript 파일들이 존재하는 src 폴더이다.

  • main.ts: 애플리케이션의 진입점(entry point). NestFactory.create()을 사용하여 새로운 Nest Application 인스턴스가 생성된다
  • app.module.ts: 애플리케이션의 root module에 대한 구현을 포함
  • app.controller.ts: 하나의 라우트만을 갖고 있는 기본 NestJS 컨트롤러의 구현을 포함
  • app.service.ts: 기본 서비스 구현을 포함
  • app.controller.spec.ts: 컨트롤러에 대한 테스팅 파일

main.ts

NestJS 애플리케이션이 어떻게 구성되고 실행되는지 알아보기 위해 애플리케이션의 진입점(Entry Point) 역할을 하는 main.ts 파일을 봐보자.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(process.env.PORT ?? 3000);
}
bootstrap();

코드의 가장 윗부분에서는 필요한 모듈들을 가져온다.

  • NestFactory: @nestjs/core 라이브러리에서 가져오는 핵심 클래스이다. 이름에서 알 수 있듯이, Nest 애플리케이션 인스턴스를 생성하는 역할을 한다.
  • AppModule: 우리 프로젝트의 루트 모듈(Root Module)이다. app.module.ts 파일에 정의되어 있으며, 애플리케이션의 모든 구성 요소를 묶어주는 역할을 한다.

bootstrap이라는 이름의 async 함수가 정의되어 있다. 이 함수는 NestJS 애플리케이션을 설정하고 실행하는 모든 로직을 포함한다.
bootstrap 함수 내부에서는 NestFactory.create() 메서드를 호출하여 Nest 애플리케이션 객체(인스턴스)를 생성한다.

  • 이때 create() 메서드의 인자로 루트 모듈인 AppModule을 전달한다. 이를 통해 NestJS는 AppModule을 시작으로 애플리케이션 전체의 의존성 관계를 파악하고 모듈, 컨트롤러, 프로바이더 등을 구성한다.
  • 만약 create() 메서드에 CatModule 같은 다른 모듈을 전달하면, 해당 모듈이 애플리케이션의 루트 모듈이 된다.

생성된 애플리케이션 인스턴스(app)는 listen() 메서드를 호출하여 특정 포트에서 들어오는 HTTP 요청을 수신 대기하기 시작한다.

  • 일반적으로 개발 환경에서는 3000 포트를 많이 사용한다.
  • listen() 메서드는 서버가 성공적으로 시작되면 Promise를 반환하므로, await 키워드를 사용하여 비동기적으로 처리한다

main.ts의 역할은 NestFactory를 사용해 최상위 모듈(AppModule)을 기반으로 Nest 애플리케이션 인스턴스를 만들고, 지정된 포트에서 서버를 실행하는 것이라고 할 수 있다.

app.module.ts

main.ts에서 NestJS 애플리케이션이 시작되면 그 애플리케이션의 중심이자 모든 것을 연결하는 루트 모듈(Root Module)이 AppModule이다. -> 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() 데코레이터: 모듈의 정의
AppModule 클래스가 일반 클래스가 아닌 NestJS 모듈로 동작하는 이유는 바로 클래스 위에 붙은 @Module() 데코레이터 덕분이다. @nestjs/common에서 가져온 이 데코레이터는 클래스를 모듈로 만들어주며, 내부에 메타데이터 객체를 전달하여 모듈을 구성한다.

@Module() 데코레이터가 받는 객체는 주로 다음과 같은 속성들을 가진다.

  • imports: 현재 모듈에서 다른 모듈의 기능을 사용하고 싶을 때, 해당 모듈들을 등록하는 배열이다. 예를 들어, UserModule의 기능을 AppModule에서 사용하려면 이 배열에 UserModule을 추가해야 한다.
  • controllers: 이 모듈에 포함된 컨트롤러들을 등록하는 배열이다. 컨트롤러는 HTTP 요청을 받고 응답을 보내는 역할을 한다. 애플리케이션에서 사용하는 모든 컨트롤러는 반드시 특정 모듈의 controllers 배열에 속해야 한다.
  • providers: 서비스(Service)와 같이 비즈니스 로직을 처리하는 클래스들을 등록하는 배열이다. 여기에 등록된 프로바이더는 의존성 주입(Dependency Injection) 을 통해 모듈 내 다른 컴포넌트(컨트롤러 등)에서 간편하게 사용할 수 있다.
  • exports: 이 모듈의 providers 중 일부를 외부 다른 모듈에서 사용할 수 있도록 공개(노출) 하는 배열이다. 여기에 등록된 프로바이더는 이 모듈을 import하는 다른 모듈에서도 사용이 가능하다

AppModule은 애플리케이션의 루트 모듈로서, 모든 구성 요소(다른 모듈, 컨트롤러, 서비스 등)를 묶어주는 접착제 역할을 한다.

애플리케이션이 처음 생성되면 AppController와 AppService가 각각 controllers와 providers 배열에 등록되어 있다. 앞으로 애플리케이션의 기능이 확장되면서 새로운 컨트롤러와 서비스가 만들어지면, 이들 역시 AppModule 또는 하위 기능 모듈에 반드시 등록되어야 NestJS가 인식하고 관리할 수 있다.

app.controller.ts

이 파일은 애플리케이션의 가장 기본적인 요청을 처리하는 관문 역할을 한다.

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

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }
}

@Controller() 데코레이터는 이 클래스가 NestJS 컨트롤러임을 나타낸다. 보통 @Controller('cats')와 같이 괄호 안에 경로(prefix)를 지정하지만, 여기서는 비어있다. 이는 AppController가 애플리케이션의 루트 경로(/)에 대한 요청을 처리한다는 것을 의미한다.

AppController는 생성자(constructor)를 통해 AppService를 주입받는다.

  • 의존성 주입 DI(Dependency Injection): AppController가 new AppService()처럼 직접 AppService의 인스턴스를 생성하는 것이 아니라, NestJS가 미리 생성해 둔 인스턴스를 주입해주는 방식이다.
  • private readonly appService: AppService: 이 코드는 TypeScript의 단축 문법이다. AppService 타입의 appService라는 멤버 변수를 선언하고, 주입받은 인스턴스를 즉시 할당하는 역할을 동시에 수행한다.

이를 통해 컨트롤러는 어떻게 서비스를 만들지 신경 쓰지 않고, 어떤 서비스를 사용할지에만 집중할 수 있게 된다.

@Get() 데코레이터는 HTTP GET 요청을 처리하는 핸들러(메서드)를 지정한다.

  • @Controller()와 마찬가지로 @Get()의 괄호 안이 비어있으므로, 컨트롤러의 루트 경로에 대한 GET 요청을 처리한다. 즉, GET / 요청이 들어오면 이 getHello() 메서드가 실행된다.
  • return this.appService.getHello(): getHello() 메서드의 핵심 로직이다. 컨트롤러가 직접 "Hello World!" 문자열을 반환하는 것이 아니라, 주입받은 appService의 getHello() 메서드를 호출하고 그 결과를 반환한다.

AppController의 코드는 NestJS의 핵심 디자인 패턴인 역할의 분리를 명확하게 보여준다.

  • 컨트롤러(AppController): 클라이언트의 HTTP 요청(GET /)을 받고, 응답을 반환하는 라우팅의 역할만 담당한다.
  • 서비스(AppService): "Hello World!"를 반환하는 것과 같은 실질적인 비즈니스 로직을 담당한다.

이처럼 컨트롤러는 요청을 서비스에게 전달하는 중개자 역할에 집중하고, 복잡한 로직은 서비스에게 위임함으로써 코드를 더 깔끔하고 테스트하기 쉽게 유지할 수 있다.

app.service.ts

AppController가 클라이언트의 요청을 받는 관문이라면, AppService는 그 요청을 실제로 처리하는 작업장이다.

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

@Injectable() 데코레이터는 AppService 클래스가 NestJS의 IoC 컨테이너(Inversion of Control Container)에 의해 관리될 수 있는 프로바이더(Provider)임을 나타낸다.
쉽게 말해, 이 데코레이터는 클래스에 "저는 다른 곳에 주입(Injection)될 수 있는 부품입니다"라는 꼬리표를 붙여주는 것과 같다. 이 꼬리표가 있어야만 NestJS의 의존성 주입 시스템이 이 서비스를 인식하고, AppController와 같은 다른 컴포넌트에 주입해 줄 수 있다.

getHello() 메서드는 AppService가 수행하는 실질적인 비즈니스 로직을 담고 있다. 이 예제에서는 단순히 'Hello World!'라는 문자열을 반환하는 간단한 작업을 수행한다.
실제 애플리케이션에서는 이와 같은 서비스 메서드 안에서 다음과 같은 복잡한 작업들이 이루어진다.

  • 데이터베이스에서 사용자 정보 조회
  • 외부 API를 호출하여 데이터 가져오기
  • 요청받은 데이터를 가공하거나 계산하기

AppController에서는 바로 이 getHello() 메서드를 호출하여 그 결과값을 클라이언트에게 응답으로 보내주게 된다.

AppService는 NestJS의 핵심 철학인 관심사의 분리(Separation of Concerns, SoC)를 잘 보여준다.

  • AppController : 어떤 요청이 들어왔는지 파악하고, 그에 맞는 담당자에게 작업을 넘긴다. (GET / 요청을 appService.getHello()에 연결)
  • AppService : 컨트롤러에게 작업을 넘겨받아 실제로 어떻게 처리할지에 대한 로직을 수행한다. ('Hello World!' 문자열을 생성하여 반환)

이처럼 각자의 역할에만 집중함으로써 코드는 훨씬 더 명확해지고, 단위 테스트가 용이하며, 유지보수하기 좋은 구조가 된다.

0개의 댓글