NestJS 모듈(Module) 구조 이해하기

NestJS 모듈이란?

모듈은 NestJS 애플리케이션의 기본 구성 단위입니다. @Module() 데코레이터로 장식된 클래스로, 관련된 기능들을 하나로 묶어 조직화합니다.

모듈의 역할

  • 애플리케이션을 논리적인 단위로 분리
  • 코드의 재사용성과 유지보수성 향상
  • 의존성 관리를 명확하게 정의
  • 애플리케이션의 구조를 체계적으로 구성

기본 모듈 구조

@Module() 데코레이터 속성

@Module({
imports: [], // 이 모듈에서 사용할 다른 모듈들
controllers: [], // 이 모듈에 속한 컨트롤러들
providers: [], // 이 모듈에서 사용할 프로바이더(서비스)들
exports: [] // 다른 모듈에서 사용할 수 있도록 내보낼 프로바이더들
})
export class MyModule {}

루트 모듈 (Root Module)

모든 NestJS 애플리케이션은 최소한 하나의 루트 모듈을 가져야 합니다.

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 {}

기능 모듈 (Feature Module)

특정 기능과 관련된 코드를 그룹화하는 모듈입니다.

예제: Users 모듈 생성

# CLI로 모듈 생성
nest g module users

users/users.module.ts

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
controllers: [UsersController],
providers: [UsersService],
exports: [UsersService], // 다른 모듈에서 UsersService를 사용할 수 있게 함
})
export class UsersModule {}

users/users.service.ts

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

@Injectable()
export class UsersService {
private users = [
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' },
];

findAll() {
return this.users;
}

findOne(id: number) {
return this.users.find(user => user.id === id);
}

create(user: { name: string; email: string }) {
const newUser = {
id: this.users.length + 1,
...user,
};
this.users.push(newUser);
return newUser;
}
}

users/users.controller.ts

import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}

@Get()
findAll() {
return this.usersService.findAll();
}

@Get(':id')
findOne(@Param('id') id: string) {
return this.usersService.findOne(+id);
}

@Post()
create(@Body() createUserDto: { name: string; email: string }) {
return this.usersService.create(createUserDto);
}
}

모듈 가져오기 (Imports)

다른 모듈에서 제공하는 기능을 사용하려면 imports 배열에 추가합니다.

posts/posts.module.ts

import { Module } from '@nestjs/common';
import { PostsController } from './posts.controller';
import { PostsService } from './posts.service';
import { UsersModule } from '../users/users.module'; // Users 모듈 가져오기

@Module({
imports: [UsersModule], // UsersService를 사용하기 위해
controllers: [PostsController],
providers: [PostsService],
})
export class PostsModule {}

posts/posts.service.ts

import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';

@Injectable()
export class PostsService {
constructor(private readonly usersService: UsersService) {}

getUserPosts(userId: number) {
const user = this.usersService.findOne(userId);
// 사용자의 게시글 조회 로직
return {
user,
posts: [],
};
}
}

공유 모듈 (Shared Module)

여러 모듈에서 공통으로 사용하는 기능을 제공하는 모듈입니다.

shared/shared.module.ts

import { Module, Global } from '@nestjs/common';
import { LoggerService } from './logger.service';
import { UtilsService } from './utils.service';

@Global() // 전역 모듈로 설정
@Module({
providers: [LoggerService, UtilsService],
exports: [LoggerService, UtilsService],
})
export class SharedModule {}

@Global() 데코레이터

  • 전역 모듈로 만들면 한 번만 import하면 모든 곳에서 사용 가능
  • 루트 모듈이나 코어 모듈에서만 import
  • 남용하지 않도록 주의 (필요한 경우에만 사용)

동적 모듈 (Dynamic Module)

런타임에 설정을 받아 동적으로 구성되는 모듈입니다.

config/config.module.ts

import { Module, DynamicModule } from '@nestjs/common';
import { ConfigService } from './config.service';

@Module({})
export class ConfigModule {
static register(options: { folder: string }): DynamicModule {
return {
module: ConfigModule,
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
ConfigService,
],
exports: [ConfigService],
};
}
}

동적 모듈 사용

import { Module } from '@nestjs/common';
import { ConfigModule } from './config/config.module';

@Module({
imports: [
ConfigModule.register({ folder: './config' }),
],
})
export class AppModule {}

모듈 재내보내기 (Re-exporting)

가져온 모듈을 다시 내보낼 수 있습니다.

@Module({
imports: [CommonModule],
exports: [CommonModule], // CommonModule을 재내보내기
})
export class CoreModule {}

실전 예제: 완전한 모듈 구조

프로젝트 구조

src/
├── app.module.ts
├── users/
│ ├── users.module.ts
│ ├── users.controller.ts
│ ├── users.service.ts
│ └── dto/
│ ├── create-user.dto.ts
│ └── update-user.dto.ts
├── posts/
│ ├── posts.module.ts
│ ├── posts.controller.ts
│ ├── posts.service.ts
│ └── dto/
│ └── create-post.dto.ts
├── auth/
│ ├── auth.module.ts
│ ├── auth.controller.ts
│ └── auth.service.ts
└── common/
├── common.module.ts
├── logger.service.ts
└── utils.service.ts

app.module.ts - 모든 모듈 통합

import { Module } from '@nestjs/common';
import { UsersModule } from './users/users.module';
import { PostsModule } from './posts/posts.module';
import { AuthModule } from './auth/auth.module';
import { CommonModule } from './common/common.module';

@Module({
imports: [
CommonModule,
UsersModule,
PostsModule,
AuthModule,
],
})
export class AppModule {}

모듈 설계 베스트 프랙티스

1. 단일 책임 원칙

각 모듈은 하나의 명확한 책임만 가져야 합니다.

// Good: 기능별로 분리
UsersModule - 사용자 관리
PostsModule - 게시글 관리
AuthModule - 인증/인가

// Bad: 너무 많은 기능을 하나의 모듈에
AppModule - 모든 기능 포함

2. 느슨한 결합

모듈 간의 의존성을 최소화합니다.

// Good: 인터페이스를 통한 의존성
@Module({
providers: [
{
provide: 'IUserRepository',
useClass: UserRepository,
},
],
})

// Bad: 구체적인 구현에 직접 의존
@Module({
providers: [UserRepository],
})

3. 명확한 경계

모듈의 공개 API를 exports로 명확히 정의합니다.

@Module({
providers: [UsersService, UserRepository], // UserRepository는 내부용
exports: [UsersService], // UsersService만 외부에 공개
})
export class UsersModule {}

모듈 생성 CLI 명령어

# 기본 모듈 생성
nest g module users

# 모듈, 컨트롤러, 서비스를 한번에 생성
nest g resource users

# 특정 폴더에 모듈 생성
nest g module modules/users

# 테스트 파일 없이 생성
nest g module users --no-spec

모듈 로딩 순서

  1. imports에 선언된 모듈들이 먼저 로드
  2. 현재 모듈의 providers 등록
  3. controllers 인스턴스화
  4. exports로 선언된 providers를 다른 모듈에서 사용 가능하게 함

순환 참조 문제 해결

모듈 간 순환 참조가 발생할 경우:

forwardRef() 사용

import { Module, forwardRef } from '@nestjs/common';
import { PostsModule } from '../posts/posts.module';

@Module({
imports: [forwardRef(() => PostsModule)],
})
export class UsersModule {}
import { Module, forwardRef } from '@nestjs/common';
import { UsersModule } from '../users/users.module';

@Module({
imports: [forwardRef(() => UsersModule)],
})
export class PostsModule {}

정리

  • 모듈은 NestJS의 핵심 구성 요소
  • @Module() 데코레이터로 정의
  • imports, controllers, providers, exports로 구성
  • 기능별로 모듈을 분리하여 관리
  • 공유 모듈과 동적 모듈을 활용하여 유연성 확보
  • 순환 참조는 forwardRef()로 해결

다음 단계

모듈 구조를 이해했다면:

  • 컨트롤러로 라우팅 처리하기
  • 서비스에서 비즈니스 로직 구현하기
  • 의존성 주입 패턴 이해하기
Share