개인의 기록
  • 소개
  • 프로젝트
  • 글
  • 링크

© 2026 newgirok

← 글 목록

NestJS 프로젝트 구조 — 실무 폴더 구성 방법

2025년 3월 26일
NestJS프로젝트 구조Module 분리Feature Module폴더 구조

NestJS는 Angular에서 영감을 받은 모듈 중심 아키텍처를 채택하고 있어, 초기 폴더 구조를 잘못 잡으면 규모가 커질수록 의존성이 얽히기 쉽다. Feature Module 단위로 코드를 분리하면 팀 단위 개발과 테스트 격리가 훨씬 수월해진다. 아래 구조는 실제 서비스에서 반복 검증된 패턴이다.

src 기본 골격

src/
├── main.ts
├── app.module.ts
├── app.controller.ts
├── app.service.ts
│
├── common/
│   ├── decorators/
│   ├── filters/
│   ├── guards/
│   ├── interceptors/
│   └── pipes/
│
├── config/
│   └── configuration.ts
│
├── shared/
│   └── shared.module.ts
│
└── features/
    ├── users/
    │   ├── users.module.ts
    │   ├── users.controller.ts
    │   ├── users.service.ts
    │   ├── dto/
    │   │   ├── create-user.dto.ts
    │   │   └── update-user.dto.ts
    │   └── entities/
    │       └── user.entity.ts
    └── posts/
        ├── posts.module.ts
        ├── posts.controller.ts
        ├── posts.service.ts
        ├── dto/
        └── entities/

main.ts — 애플리케이션 진입점

main.ts는 NestFactory로 앱 인스턴스를 생성하고 리스닝을 시작하는 단일 책임 파일이다. 전역 Pipe, Guard, Interceptor 설정도 여기서 등록한다.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  app.setGlobalPrefix('api');
  await app.listen(3000);
}
bootstrap();

app.module.ts — 루트 모듈

루트 모듈은 Feature Module들을 조합하는 조율자 역할만 해야 한다. 비즈니스 로직을 직접 넣지 않는다.

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { UsersModule } from './features/users/users.module';
import { PostsModule } from './features/posts/posts.module';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    UsersModule,
    PostsModule,
  ],
})
export class AppModule {}

Feature Module 내부 구성

각 Feature 폴더는 controller / service / dto / entities 네 레이어로 구성된다.

파일역할
*.module.ts의존성 주입 컨텍스트 선언
*.controller.tsHTTP 라우팅, 요청/응답 처리
*.service.ts비즈니스 로직, 트랜잭션
dto/*.dto.ts요청 데이터 유효성 검증
entities/*.entity.tsDB 스키마 매핑 (TypeORM 등)

DTO(Data Transfer Object): 계층 간 데이터 이동 시 형태를 강제하는 클래스. class-validator 데코레이터와 함께 사용한다.

common / config / shared 폴더 역할

common 폴더에는 특정 Feature에 종속되지 않는 재사용 요소를 둔다. Guard, Interceptor, Pipe, Decorator가 여기 해당한다.

config 폴더는 환경변수 스키마와 ConfigModule 설정 파일을 관리한다. configuration.ts에서 process.env 값을 타입 안전하게 래핑한다.

shared 폴더는 여러 Feature Module이 공통으로 주입받는 Provider(예: 이메일 서비스, 파일 업로드 헬퍼)를 SharedModule로 묶어 export한다.

@Module({
  providers: [MailService, StorageService],
  exports: [MailService, StorageService],
})
export class SharedModule {}

SharedModule을 AppModule에서 imports하면 다른 Feature Module에서 별도 import 없이 주입받을 수 있다. 단, 순환 의존이 생기지 않도록 SharedModule은 Feature Module을 import하지 않아야 한다.

Feature Module 간 의존 규칙

AppModule
  ├── UsersModule  ←──┐
  ├── PostsModule  ───┘ (PostsModule이 UsersService 필요 시)
  └── SharedModule (공통 Provider export)

Feature Module이 다른 Feature의 Service를 필요로 할 때는, 해당 모듈에서 명시적으로 exports에 추가하고 소비하는 모듈의 imports에 등록한다. 이 규칙을 지키면 의존 방향이 항상 명시적으로 드러난다.

← 이전 글NestJS Testing — 단위 테스트와 E2E 테스트
다음 글 →Next.js란? — React 위에 올린 풀스택 웹 프레임워크