| 서론
안녕하세요, 팡일입니다.
프론트엔드 개발을 하면서 TypeScript에 점점 익숙해지다 보니, TypeScript를 활용한 백엔드 개발도 경험해보고 싶다는 생각이 들었습니다.
그래서 여러 백엔드 프레임워크를 찾아보던 중, 구조적인 설계와 생산성을 동시에 잡을 수 있는 NestJS를 알게 되었습니다.
이번 포스팅에서는 제가 NestJS를 학습하면서 기본 구조부터 실제 사용 방법까지 정리한 내용을 공유해보려고 합니다.
유의드리고 싶은 점
- 아직 학습 과정에 있는 만큼 부족한 부분이나 잘못된 내용이 있을 수 있습니다.
- 혹시 보시면서 개선할 점이 있다면, 편하게 의견 남겨주시면 감사하겠습니다!
| NestJS란?
1) 설명
NestJS는 Node.js 기반의 백엔드 프레임워크로, TypeScript를 중심으로 설계된 구조적인 서버 개발 프레임워크입니다.
Express 위에서 동작하지만, 단순한 서버가 아니라 대규모 애플리케이션을 위한 아키텍처를 제공한다는 점이 특징입니다.
특히 NestJS는 다음과 같은 특징을 가지고 있습니다.
- TypeScript 기반 (타입 안정성)
- 모듈 기반 구조 (기능 단위 분리)
- 의존성 주입(DI) 지원
- 데코레이터 기반 설계
한마디로 정리하면 "구조를 강제해서 유지보수하기 쉬운 백엔드를 만들게 해주는 프레임워크"입니다.
2) Spring Boot와 차이점
NestJS는 자주 Spring Boot와 비교되는 프레임워크입니다.
그 이유는 구조와 철학이 매우 유사하기 때문입니다.
| 구분 | NestJS | Spring Boot |
| 언어 | TypeScript (Node.js) | Java |
| 실행 환경 | Node.js | JVM |
| 구조 | Module / Controller / Service | Controller / Service / Repository |
| DI (의존성 주입) | 지원 | 지원 |
| 데코레이터 | 사용 (@Controller 등) | 어노테이션 (@RestController 등) |
| 진입장벽 | 비교적 낮음 | 상대적으로 높음 |
핵심적인 차이를 정리해보면 다음과 같습니다.
- NestJS → 가볍고 빠르게 개발 가능 (JavaScript 생태계)
- Spring Boot → 더 안정적이고 전통적인 엔터프라이즈 구조
3) 왜 NestJS를 사용하는가?
단순히 Express만 사용할 수도 있는데, 왜 굳이 NestJS를 사용할까요?
이유는 “구조” 때문입니다. Express는 자유도가 높은 대신 프로젝트가 커질수록 구조가 흐트러질 수 있습니다.
반면 NestJS는 폴더 구조, 역할 분리, 의존성 관리를 강제하기 때문에 처음부터 유지보수 가능한 구조를 만들 수 있습니다.
| NestJS 기본 구조
NestJS는 서버 애플리케이션을 기능과 역할에 따라 명확하게 분리하여 구성합니다. 이러한 구조 덕분에 코드의 가독성이 높아지고, 유지보수가 훨씬 쉬워집니다.
특히 프로젝트 규모가 커질수록 구조가 잘 잡혀 있는지 여부가 매우 중요해지는데, NestJS는 이를 처음부터 체계적으로 설계할 수 있도록 도와줍니다.
1) 핵심 구성 요소
NestJS는 크게 다음 3가지 요소를 중심으로 돌아갑니다.
| 핵심 구성요소 | 역할 | 비유 |
| Controller | 요청(HTTP Request) 받기 & 응답 반환 | 프론트 직원 (주문 받기) |
| Service | 실제 비즈니스 로직 처리 | 주방 요리사 (실제 일 수행) |
| Module | 관련된 Controller와 Service 묶음 | 메뉴 카테고리/부서 (조직화) |
- 컨트롤러(Controller): 클라이언트 요청을 받고 응답을 돌려주는 곳
- 서비스(Service): 비즈니스 로직 처리, 데이터 연산, DB 통신 담당
- 모듈(Module): 컨트롤러와 서비스를 묶어 애플리케이션 기능 단위로 구성
2) 구조 흐름 이해하기

위와 같이 NestJS의 전체 흐름은 다음과 같이 동작합니다.
클라이언트 요청
→ Controller (요청 수신)
→ Service (비즈니스 로직 처리)
→ Database / API / 내부 로직 수행
→ Controller (결과 반환)
→ 클라이언트 응답
| 프로젝트 세팅
NestJS 프로젝트는 CLI를 통해 간단하게 생성할 수 있으며, 기본적인 서버 구조와 실행 환경이 자동으로 구성됩니다.
1) 프로젝트 생성
nest new first-nestjs
명령어를 실행하면 새로운 Nest 프로젝트가 생성되는데요? 아래와 같이 패키지 매니저까지 선택해야 합니다.
? Which package manager would you ❤️ to use?
❯ npm
yarn
pnpm
원하는 패키지 매니저를 선택하면, 의존성 설치 및 프로젝트 초기 세팅이 자동으로 진행됩니다.

위와 같은 결과가 나온다면, 정상적으로 설치가 완료된 것입니다.
2) 기본 생성 파일 구조 이해
프로젝트가 생성되면 Nest는 기본적으로 아래와 같은 핵심 파일을 제공합니다.
(1) main.ts
: 애플리케이션의 시작점(Entry Point)
- Nest 애플리케이션이 가장 먼저 실행되는 파일
- NestFactory를 통해 서버를 생성
- AppModule을 주입하여 전체 앱 구조를 연결
- 기본적으로 3000번 포트에서 서버 실행
(2) app.modules.ts
: 애플리케이션의 루트 모듈
- Nest 애플리케이션의 중심이 되는 모듈
- 여러 기능(Module)을 하나로 묶는 역할
- controllers
→ 요청을 처리하는 Controller 등록 - providers
→ Service(비즈니스 로직)를 주입하는 곳
(3) app.service.ts
: 비즈니스 로직을 담당하는 서비
- 실제 로직을 처리하는 클래스
- Controller에서 호출하여 사용됨
즉, main.ts → AppModule → Controller → Service → 응답 반환 이라는 흐름으로 전체 실행 흐름이 세워지게 됩니다.
| 파일 네이밍 컨벤션
NestJS에서는 일관된 파일 네이밍 규칙을 통해 프로젝트 구조를 쉽게 이해하고 유지보수를 용이하게 합니다.
1) 기본 파일 네이밍 규칙
: NestJS는 아래와 같은 규칙을 기반으로 파일명을 작성합니다.
(1) 소문자 사용
: 모든 파일명은 소문자 사용
(2) 단어 구분
: 하이픈으로 단어 구분
(3) 파일 확장자
: 항상 .ts
(4) 역할 접미사
: 파일 역할에 맞는 접미사 추가
2) 주요 파일 타입별 네이밍 규칙
NestJS에서 자주 사용하는 파일들의 네이밍 규칙입니다.
| 파일 종류 | 접미사 | 예시 | 설명 |
| 모듈 (Module) | *.module.ts | user.module.ts | Nest 모듈 정의 파일 |
| 컨트롤러 (Controller) | *.controller.ts | auth.controller.ts | 요청/응답 처리 |
| 서비스 (Service) | *.service.ts | user.service.ts | 비즈니스 로직 구현 |
| 엔티티 (Entity) | *.entity.ts | user.entity.ts | DB 테이블 매핑 클래스 |
| 인터페이스 (Interface) | *.interface.ts | user.interface.ts | 타입 정의 |
| 데이터 전송 객체 (DTO) | *.dto.ts | sign-in.dto.ts | 입력/출력 데이터 구조 |
| 파이프 (Pipe) | *.pipe.ts | validation.pipe.ts | 데이터 변환 및 검증 |
| 가드 (Guard) | *.guard.ts | auth.guard.ts | 인증/인가 처리 |
| 인터셉터 (Interceptor) | *.interceptor.ts | logging.interceptor.ts | 요청/응답 가로채기 |
| 필터 (Filter) | *.filter.ts | http-exception.filter.ts | 예외 처리 로직 |
| 테스트 파일 (Test) | *.spec.ts | user.service.spec.ts | 단위 테스트 코드 |
3) 디렉토리 구조 예시
NestJS는 기능(도메인)*단위로 폴더를 나누고, 그 안에서 역할별 파일을 구성하는 방식을 사용합니다.
src/
├── user/
│ ├── user.module.ts # 모듈 (Controller + Service 묶음)
│ ├── user.controller.ts # 요청/응답 처리
│ ├── user.service.ts # 비즈니스 로직
│ ├── user.entity.ts # DB 테이블 매핑
│ ├── user.interface.ts # 타입 정의
│ ├── dto/
│ │ └── sign-in.dto.ts # 요청/응답 데이터 구조
│ ├── pipes/
│ │ └── validation.pipe.ts # 데이터 검증
│ ├── guards/
│ │ └── auth.guard.ts # 인증/인가 처리
│ ├── interceptors/
│ │ └── logging.interceptor.ts # 요청/응답 가로채기
│ ├── filters/
│ │ └── http-exception.filter.ts # 예외 처리
│ └── user.service.spec.ts # 테스트 코드
│
├── app.module.ts # 루트 모듈
└── main.ts # 앱 시작 진입점
| 데코레이터 개념과 활용
1) 데코레이터(Decorator)란?
: 어떤 코드가 어떤 역할을 하는지 Next.js에게 알려주는 표시를 말합니다.
2) 데코레이터가 붙는 위체에 따른 동작
: 데코레이터는 붙은 위치(클래스, 메서드, 속성, 매개 변수)등에 따라 다르게 동작하고, NestJS는 데코레이터를 읽고 내부적으로 기능을 자동으로 처리합니다.
import { Controller, Get, Inject, Param } from '@nestjs/common';
import { UsersService } from './users.service';
// 1. 클래스 데코레아터
// 이 클래스가 컨트롤러이며, 기본 경로가 '/users'임을 Next.js에게 알린다.
@Controller('users')
export class UserController {
// 2. 속성 데코레이터
// Next.js가 ConfigService 인스턴스를 자동으로 주입해준다 (DI)
@Inject(ConfigService)
private readonly configService: ConfigService;
// 생성자 주입
// UsersService를 자동으로 의존성 주입을 받는다.
constructor(private readonly usersService: UsersService) {}
// 3. 메서드 데코레이터
// 이 메서드는 GET /user/:id 요청을 처리한다.
@Get(':id')
findOne(
// 4. 매개변수 데코레이터
// 요청 겅로에서 id 값을 추출하여 매개변수에 주입한다.
@Param('id') id: string,
) {
return `User ID: ${id}`;
}
}
3) 데코레이터의 종류 정리
| 구분 | 예시(종류) | 설명 |
| 클래스 데코레이터 | @Controller(), @Injectable(), @Module() | 클래스 전체의 역할을 지정 (예: 컨트롤러, 서비스, 모듈 등으로 선언) |
| 메서드 데코레이터 | @Get(), @Post(), @Put(), @Delete() | HTTP 요청 메서드에 따라 어떤 요청을 처리할지 지정 |
| 속성 데코레이터 | @Inject(), @InjectRepository() | 클래스 내부 속성에 의존성을 주입할 때 사용 |
| 매개변수 데코레이터 | @Param(), @Body(), @Query() | 요청(Request) 객체에서 필요한 데이터를 꺼내 메서드 매개변수로 전달 |
| 컨트롤러로 배우는 CRUD API 실습
import {
Body,
Controller,
Delete,
Get,
Param,
Post,
Put,
Query,
} from '@nestjs/common';
@Controller('users')
export class UserController {
/**
* 새로운 사용자 등록
* 예) POST /user {"email" : "email@gmail.com", "password" : "password"}
*/
@Post()
create(@Body() body: { email: string; password: string }) {
return `새로운 사용자 생성 : ${body.email}`;
}
/**
* 전체 사용자 목록 조회
* 예) GET /users?page=2
*/
@Get()
findAll(@Query('page') page?: number) {
const currentPage = page || 1;
return `모든 사용자 조회 (현재 페이지 : ${currentPage})`;
}
/**
* 특정 사용자 조회
* 예) GET /users/5
*/
@Get(':id')
findOne(@Param('id') id: string) {
return `사용자 조회 (ID : ${id})`;
}
/**
* 특정 사용자 정보 수정
* 예) PUT /users/5 {"name" : "혿길동"}
*/
@Put(':id')
update(@Param() id: string, @Body() body: { name: string }) {
return `사용자 ID-${id} 수정 -> 이름 : ${body.name}`;
}
/**
* 사용자 삭제
* 예) DELETE /user/5
*/
@Delete(':id')
remove(@Param('id') id: string) {
return `사용자 삭제(ID : ${id})`;
}
}
| CLI로 NestJS 파일 만들기
NestJS에서는 CLI를 통해 반복적인 파일 생성을 자동화할 수 있습니다. 이를 활용하면 일관된 구조를 유지하면서 빠르게 개발을 시작할 수 있습니다.
1) 기본 명령어
nest <command> [options]
: Nest CLI의 모든 명령어는 위 형태를 따르며, command 자리에 원하는 기능을 입력하여 사용합니다.
2) 주요 명령어
Nest 프로젝트를 생성하고 실행하는 데 자주 사용하는 핵심 명령어입니다.
| 명령어 | 설명 |
| nest new (n) | 새로운 Nest 앱 생성 |
| nest build | 프로젝트 빌드 |
| nest start | 서버 실행 |
| nest info (i) | 프로젝트 정보 확인 |
| nest add <library> | 외부 라이브러리 추가 |
| nest generate (g) | 파일/구조 자동 생성 |
: 특히 generate (g) 명령어는 실무에서 가장 많이 사용하는 핵심 기능입니다.
3) generate(g) 상세
: Nest에서는 generate 명령어를 통해 Controller, Service, Module 등 다양한 파일을 자동 생성할 수 있습니다.
(1) 기본 명령어
nest g <type> <name>
- <type> : 생성할 파일 종류
- <name> : 기능 또는 도메인 이름
(2) 타입 목록
: 아래는 Nest CLI가 제공하는 주요 생성 타입입니다.
| 타입 | alias | 설명 |
| application | application | 새로운 앱 생성 (monorepo) |
| class | cl | 클래스 생성 |
| configuration | config | 설정 파일 생성 |
| controller | co | 컨트롤러 생성 |
| decorator | d | 커스텀 데코레이터 생성 |
| filter | f | 필터 생성 |
| gateway | ga | 게이트웨이 생성 (WebSocket) |
| guard | gu | 가드 생성 |
| interceptor | itc | 인터셉터 생성 |
| interface | itf | 인터페이스 생성 |
| library | lib | 라이브러리 생성 (monorepo) |
| middleware | mi | 미들웨어 생성 |
| module | mo | 모듈 생성 |
| pipe | pi | 파이프 생성 |
| provider | pr | provider 생성 |
| resolver | r | GraphQL resolver 생성 |
| resource | res | CRUD 구조 자동 생성 |
| service | s | 서비스 생성 |
| sub-app | app | monorepo 내부 앱 생성 |
(3) 실제 사용 예시
: 가장 기본적인 CRUD 기능을 만들기 위해 board라는 도메인을 생성해보겠습니다.
nest g module board
nest g controller board
nest g service board
위 명령어를 실행하면 Nest는 자동으로 파일 생성 + 모듈 연결까지 처리해줍니다.
// 최종 폴더 구조
src/
├── board/
│ ├── board.module.ts
│ ├── board.controller.ts
│ ├── board.controller.spec.ts
│ ├── board.service.ts
│ ├── board.service.spec.ts
키포인트는 바로, controller, service 생성 시 자동으로 module에 등록되는 것도 중요한 특징입니다.
| Controller-Service 구조와 의존성 주입(DI)
NestJS에서는 Controller와 Service를 분리하여 역할을 명확하게 나누고, Service를 Controller에 주입(Dependency Injection)하여 사용하는 구조를 가집니다.
Controller는 요청/응답을 처리하고, Service는 비즈니스 로직을 처리한다고 보면 이해하기 쉽습니다.
1) Service 파일 작성
import { Injectable } from '@nestjs/common';
@Injectable()
export class BoardService {
findAll() {
return 'findAll 호출';
}
findOne(id: string) {
return `게시물 조회 (ID : ${id})`;
}
}
@Injectable() 키워드를 사용하여 Service 클래스를 선언하며, 해당 클래스를 Nest가 관리하는 provider로 등록합니다.
이를 통해 다른 클래스에서 주입받아 사용할 수 있음
2) Controller 파일 수정
import { Controller, Get, Param } from '@nestjs/common';
import { BoardService } from './board.service';
@Controller('board')
export class BoardController {
constructor(private readonly boardService: BoardService) {}
@Get()
findAll() {
return this.boardService.findAll();
}
@Get(':id')
findOne(@Param('id') id: string) {
return this.boardService.findOne(id);
}
}
Controller에서는 constructor를 통해 Service를 주입받아 사용합니다.
constructor(private readonly boardService: BoardService) {}
- private : 매개변수로 받은 값을 클래스 내부의 private 필드로 자동 선언
- readonly : 이후에 이 속성을 수정할 수 없도록(읽기 전용) 설정
- boardService : 실제 주입받을 객체의 이름 (이름은 자유롭게 작성할 수 있지만, 일반적으로 클래스명과 기반으로 작성)
- BoardService : 주입받을 서비스의 타입(NestJS가 이 타입을 보고 인스턴스를 찾아서 주입)
즉 클라이언트 요청 -> Controller -> Service -> 결과 반환 와 같은 흐름으로 진행이 되며, Controller와 Service를 연결하는 것이 의존성 주입(DI)입니다.
| 결론
이번 글에서는 Nest.js의 기본 구조부터 시작해서, 프로젝트 세팅, 파일 네이밍 컨벤션, 데코레이터 개념, CLI 사용법, 그리고 Controller-Service 구조까지 전체적인 흐름을 정리해보았습니다.
Nest.js를 학습하면서 가장 인상 깊었던 점은 역할이 명확하게 분리된 구조와 CLI를 통한 생산성 높은 개발 경험이었습니다.
특히 Controller와 Service를 분리하고, 의존성 주입(DI)을 통해 유연하게 구조를 연결하는 방식은 프론트엔드에서 컴포넌트와 로직을 분리하는 것과도 유사하게 느껴졌습니다.
아직은 기본적인 구조를 이해하는 단계이지만, 앞으로는 DTO, Validation, Database 연동까지 확장하면서 보다 실제 서비스에 가까운 구조로 발전시켜보고자 합니다.
Nest.js를 처음 접하시는 분들께 이 글이 전체 흐름을 이해하는 데 작은 도움이 되었기를 바랍니다!
'💻 개발 > 🌸 NestJS' 카테고리의 다른 글
| [NestJS] DTO와 유효성 검사 (0) | 2026.03.27 |
|---|---|
| [NestJS] NestJS에서 Swaager 사용하기 (0) | 2026.03.27 |
| [NestJS] 데코레이터 CRUD 이해하기 (0) | 2026.03.27 |
