Nest.js는 Node.js 런타임 위에서 동작하는 TypeScript용 오픈 소스 백엔드 웹 프레임워크이다.
설치 시 따로 설정을 통해 JavaScript로도 사용 가능하다.
Nest.js를 보면 Java의 Spring처럼 기본적인 틀이 있고 정해진 규칙이 있어 뭔가 익숙함을 느끼게 된다. Nest.js는 Node.js를 사용하며 기본적인 규칙과 제약을 공유함으로써 다양한 프로젝트를 진행하더라도 생산성의 향상과 유지보수의 증가로 이어진다.
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 폴더가 추가되며 모듈, 컨트롤러, 서비스가 추가된다.
모듈이 자체적으로 연결되어 있다.
프로젝트를 만들고 싶은 폴더로 이동하여 아래 명령어를 실행한다
nest new new-project
new-project 부분에는 본인이 정한 프로젝트 명을 알아서 넣으면 된다.
* 새로운 NestJS 프로젝트를 현재 위치의 new-project라는 이름의 폴더 안에 생성할 것임
생성한 프로젝트로 이동하여
npm run start:dev
를 실행하고 ocalhost:3000로 접속해보면 "Hello, world!"를 확인할 수 있다.
:dev: 개발(development) 모드로 애플리케이션을 시작한다.
서버를 시작할 때 nodemon 스크립트가 사용된다.
nodemon: 코드의 변화를 감지하고 있다가 우리가 코드가 수정되었을 때, 이를 감지하여 자동으로 서버를 재시작하여 수정사항이 서버에 반영되도록 하는 기능
처음 프로젝트를 생성하면 아래와 같은 구조로 이루어져있다.

프로젝트에서 제일 중요한 폴더는 아무래도 TypeScript 파일들이 존재하는 src 폴더이다.
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();
코드의 가장 윗부분에서는 필요한 모듈들을 가져온다.
bootstrap이라는 이름의 async 함수가 정의되어 있다. 이 함수는 NestJS 애플리케이션을 설정하고 실행하는 모든 로직을 포함한다.
bootstrap 함수 내부에서는 NestFactory.create() 메서드를 호출하여 Nest 애플리케이션 객체(인스턴스)를 생성한다.
생성된 애플리케이션 인스턴스(app)는 listen() 메서드를 호출하여 특정 포트에서 들어오는 HTTP 요청을 수신 대기하기 시작한다.
main.ts의 역할은 NestFactory를 사용해 최상위 모듈(AppModule)을 기반으로 Nest 애플리케이션 인스턴스를 만들고, 지정된 포트에서 서버를 실행하는 것이라고 할 수 있다.
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() 데코레이터가 받는 객체는 주로 다음과 같은 속성들을 가진다.
AppModule은 애플리케이션의 루트 모듈로서, 모든 구성 요소(다른 모듈, 컨트롤러, 서비스 등)를 묶어주는 접착제 역할을 한다.
애플리케이션이 처음 생성되면 AppController와 AppService가 각각 controllers와 providers 배열에 등록되어 있다. 앞으로 애플리케이션의 기능이 확장되면서 새로운 컨트롤러와 서비스가 만들어지면, 이들 역시 AppModule 또는 하위 기능 모듈에 반드시 등록되어야 NestJS가 인식하고 관리할 수 있다.
이 파일은 애플리케이션의 가장 기본적인 요청을 처리하는 관문 역할을 한다.
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를 주입받는다.
이를 통해 컨트롤러는 어떻게 서비스를 만들지 신경 쓰지 않고, 어떤 서비스를 사용할지에만 집중할 수 있게 된다.
@Get() 데코레이터는 HTTP GET 요청을 처리하는 핸들러(메서드)를 지정한다.
AppController의 코드는 NestJS의 핵심 디자인 패턴인 역할의 분리를 명확하게 보여준다.
- 컨트롤러(AppController): 클라이언트의 HTTP 요청(GET /)을 받고, 응답을 반환하는 라우팅의 역할만 담당한다.
- 서비스(AppService): "Hello World!"를 반환하는 것과 같은 실질적인 비즈니스 로직을 담당한다.
이처럼 컨트롤러는 요청을 서비스에게 전달하는 중개자 역할에 집중하고, 복잡한 로직은 서비스에게 위임함으로써 코드를 더 깔끔하고 테스트하기 쉽게 유지할 수 있다.
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!'라는 문자열을 반환하는 간단한 작업을 수행한다.
실제 애플리케이션에서는 이와 같은 서비스 메서드 안에서 다음과 같은 복잡한 작업들이 이루어진다.
AppController에서는 바로 이 getHello() 메서드를 호출하여 그 결과값을 클라이언트에게 응답으로 보내주게 된다.
AppService는 NestJS의 핵심 철학인 관심사의 분리(Separation of Concerns, SoC)를 잘 보여준다.
- AppController : 어떤 요청이 들어왔는지 파악하고, 그에 맞는 담당자에게 작업을 넘긴다. (GET / 요청을 appService.getHello()에 연결)
- AppService : 컨트롤러에게 작업을 넘겨받아 실제로 어떻게 처리할지에 대한 로직을 수행한다. ('Hello World!' 문자열을 생성하여 반환)
이처럼 각자의 역할에만 집중함으로써 코드는 훨씬 더 명확해지고, 단위 테스트가 용이하며, 유지보수하기 좋은 구조가 된다.