Nest.js - Provider & Module 심화

0
post-thumbnail

NestJS Provider & Module 심화 과정 정리

1. Provider 심화 과정

1.1 Provider의 기본 개념

Provider는 NestJS의 의존성 주입(DI) 시스템의 핵심입니다. Provider는 다음과 같은 것들을 제공할 수 있습니다:

  • 서비스 (Service)
  • 저장소 (Repository)
  • 팩토리 (Factory)
  • 헬퍼 (Helper)
  • 기타 값들

1.2 Provider의 종류

1.2.1 Class Provider (기본)

// src/user/user.module.ts
@Module({
  providers: [UserService], // 클래스 자체를 Provider로 등록
})
export class UserModule {}

1.2.2 Value Provider

// src/user/user.module.ts
const UserMockService = {
  findAll: () => {
    return 'find mock users';
  },
};

@Module({
  providers: [
    {
      provide: UserService, // 토큰
      useValue: UserMockService, // 실제 값
    },
  ],
})
export class UserModule {}

설명:

  • provide: 의존성 주입 시 사용할 토큰
  • useValue: 실제로 주입될 값 (객체, 함수, 원시값 등)
  • 테스트나 모킹 시 유용하게 사용

1.2.3 Factory Provider

// src/auth/auth.module.ts
@Module({
  imports: [
    JwtModule.registerAsync({
      inject: [ConfigService], // 주입받을 의존성들
      useFactory: async (configService: ConfigService) => {
        return {
          global: true,
          secret: configService.get('jwt.secret'),
          signOptions: { expiresIn: '1d' },
        };
      },
    }),
  ],
})
export class AuthModule {}

설명:

  • inject: 팩토리 함수에 주입할 의존성들의 배열
  • useFactory: 실제 Provider 인스턴스를 생성하는 함수
  • 동적으로 설정값을 가져와서 Provider를 생성할 때 사용

1.2.4 Class Provider (useClass)

// src/user/user.service.spec.ts
const module: TestingModule = await Test.createTestingModule({
  providers: [
    UserService,
    {
      provide: getRepositoryToken(User),
      useClass: MockRepository, // MockRepository 클래스의 인스턴스 생성
    },
  ],
}).compile();

설명:

  • useClass: 지정된 클래스의 인스턴스를 생성하여 주입
  • 테스트 시 실제 Repository 대신 Mock Repository를 사용할 때 유용

1.3 전역 Provider (APP_GUARD)

// src/auth/auth.module.ts
@Module({
  providers: [
    AuthService,
    JwtStrategy,
    {
      provide: APP_GUARD, // 전역 가드 토큰
      useClass: JwtAuthGuard, // 전역적으로 적용될 가드 클래스
    },
  ],
})
export class AuthModule {}

설명:

  • APP_GUARD: NestJS에서 제공하는 특별한 토큰
  • 모든 컨트롤러와 라우트 핸들러에 자동으로 적용
  • 별도의 @UseGuards() 데코레이터 없이도 전역 인증 적용

2. Module 심화 과정

2.1 ConfigModule 설정

2.1.1 기본 ConfigModule 설정

// src/app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true, // 전역 모듈로 설정
      load: [postgresConfig, jwtConfig], // 커스텀 설정 로드
    }),
  ],
})
export class AppModule {}

설명:

  • isGlobal: true: 모든 모듈에서 ConfigService를 주입받을 수 있음
  • load: 커스텀 설정 파일들을 로드

2.1.2 커스텀 설정 파일 생성

// src/config/postgres.config.ts
import { registerAs } from '@nestjs/config';

export default registerAs('postgres', () => ({
  host: process.env.POSTGRES_HOST || 'localhost',
  port: process.env.POSTGRES_PORT ? Number(process.env.POSTGRES_PORT) : 5434,
  database: process.env.POSTGRES_DATABASE || 'postgres',
  username: process.env.POSTGRES_USERNAME || 'postgres',
  password: process.env.POSTGRES_PASSWORD || 'postgres',
}));

설명:

  • registerAs: 네임스페이스를 가진 설정 객체 생성
  • 환경변수에서 값을 가져오고, 기본값 설정
  • configService.get('postgres.host') 형태로 접근 가능
// src/config/jwt.config.ts
import { registerAs } from '@nestjs/config';

export default registerAs('jwt', () => ({
  secret: process.env.JWT_SECRET || 'temp secret',
}));

2.2 Async Module 설정

2.2.1 TypeOrmModule.forRootAsync

// src/app.module.ts
TypeOrmModule.forRootAsync({
  inject: [ConfigService], // 주입받을 의존성
  useFactory: async (configService: ConfigService) => {
    let obj: TypeOrmModuleOptions = {
      type: 'postgres',
      host: configService.get('postgres.host'),
      port: configService.get('postgres.port'),
      database: configService.get('postgres.database'),
      username: configService.get('postgres.username'),
      password: configService.get('postgres.password'),
      autoLoadEntities: true,
    };
    
    // 환경에 따른 조건부 설정
    if (configService.get('STAGE') === 'local') {
      console.info('Sync postgres');
      obj = Object.assign(obj, {
        synchronize: true,
        logging: true,
      });
    }
    return obj;
  },
}),

설명:

  • inject: 팩토리 함수에 주입할 의존성들
  • useFactory: 동적으로 설정을 생성하는 함수
  • 환경변수에 따라 다른 설정 적용 가능

2.2.2 JwtModule.registerAsync

// src/auth/auth.module.ts
JwtModule.registerAsync({
  inject: [ConfigService],
  useFactory: async (configService: ConfigService) => {
    return {
      global: true,
      secret: configService.get('jwt.secret'),
      signOptions: { expiresIn: '1d' },
    };
  },
}),

설명:

  • JWT 설정을 환경변수에서 동적으로 가져옴
  • global: true로 전역 모듈 설정

2.3 모듈 간 의존성 주입

2.3.1 ConfigService 주입

// src/auth/jwt.strategy.ts
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get('jwt.secret'), // 설정에서 JWT 시크릿 가져오기
    });
  }
}

설명:

  • ConfigService를 주입받아 설정값 사용
  • 하드코딩된 값 대신 환경변수 기반 설정 사용

2.4 모듈 구조 정리

2.4.1 AppModule (루트 모듈)

// src/app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      load: [postgresConfig, jwtConfig],
    }),
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => {
        // 동적 설정 로직
      },
    }),
    AuthModule,
    UserModule,
    VideoModule,
    AnalyticsModule,
  ],
})
export class AppModule {}

2.4.2 Feature Module (기능별 모듈)

// src/user/user.module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])], // 엔티티 등록
  exports: [UserService], // 다른 모듈에서 사용할 서비스 내보내기
  controllers: [UserController],
  providers: [UserService], // 또는 커스텀 Provider 설정
})
export class UserModule {}

3. 실제 사용 예시

3.1 환경변수 설정

# package.json
"start:dev": "cross-env STAGE=local nest start --watch"

3.2 환경변수 확인

// src/main.ts
console.info(`STAGE: ${process.env.STAGE}`);

3.3 조건부 설정 적용

if (configService.get('STAGE') === 'local') {
  console.info('Sync postgres');
  obj = Object.assign(obj, {
    synchronize: true,
    logging: true,
  });
}

4. Provider & Module 심화 과정의 장점

4.1 유연성

  • 동적 설정으로 환경별 다른 설정 적용 가능
  • 테스트 시 Mock 객체 쉽게 주입 가능

4.2 재사용성

  • 설정을 모듈화하여 여러 곳에서 재사용
  • 공통 로직을 Provider로 분리

4.3 유지보수성

  • 설정 변경 시 한 곳에서만 수정
  • 의존성 주입으로 결합도 낮춤

4.4 테스트 용이성

  • Mock Provider로 쉽게 테스트 환경 구성
  • 실제 의존성 대신 테스트용 객체 주입 가능

5. 주의사항

5.1 순환 의존성

  • 모듈 간 순환 참조 주의
  • forwardRef() 사용하여 해결 가능

5.2 전역 모듈

  • isGlobal: true 설정 시 모든 모듈에서 접근 가능
  • 과도한 전역 모듈 사용 지양

5.3 환경변수

  • 민감한 정보는 환경변수로 관리
  • 기본값 설정으로 안정성 확보

이러한 Provider와 Module 심화 과정을 통해 NestJS 애플리케이션의 구조를 더욱 견고하고 유연하게 만들 수 있습니다.

profile
하루하루 기록하기!

0개의 댓글