Nest.js의 모듈은 평범하게 다음과 같이 생겼습니다.
@Module({
imports: [],
controllers: [],
providers: [],
})
export class AppModule { }
그리고 imports 부분에 값들이 다양하게 들어가기 시작합니다.
@Module({
imports: [
ConfigModule.forRoot()
],
})
또는
import someConfig from "./config/some.config";
@Module({
imports: [
ConfigModule.forFeature(someConfig)
],
})
그리고
@Module({
imports: [
ConfigModule.register({ folder: './config' })
],
})
export const AppModule
@Module({})
export class ConfigModule {
static register(): DynamicModule {
return {
module: ConfigModule,
providers: [ConfigService],
exports: [ConfigService],
};
}
}
등 그냥 Module 을 import 해 올 것이지
뭐가 메서드가 그냥 많습니다.
위 세가지 내용을 정리하자면
이는 가장 처음에 연결해야하는 것들
그리고 대부분의 영역에서 사용될 것들입니다.
ConfigModule.forRoot()
TypeOrmModule.forRoot({})
MongooseModule.forRoot('mongodb://localhost/nest')
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
})
대부분이 기초 설정에 의거한 내용들이더라고요.
물론 인자값을 전달하기에 동적모듈을 리턴합니다.
동적모듈이라고 어려울게 아닌, 그저 그냥 모듈 구성에 인자값으로 인한 변화만 있을뿐이다
라고 생각하시면 좋습니다.
이는 동적 모듈을 제공하기 위해서 사용됩니다.
register에 넣어주는 인자값에 의하여 제공되는 모듈의 구성이 다른거죠.
@Module({})
export class ConfigModule {
static register(options: Record<string, any>): DynamicModule {
return {
module: ConfigModule,
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: options,
},
ConfigService,
],
exports: [ConfigService],
};
}
}
다음처럼 제공자에 동적인 값들을 넣어 줄 수 있습니다.
그리고 그것은 다음과 같은 결과를 가져올 가능성이 있습니다.
@Injectable()
export class ConfigService {
private readonly envConfig: EnvConfig;
constructor(@Inject('CONFIG_OPTIONS') private options: Record<string, any>) {
const filePath = `${process.env.NODE_ENV || 'development'}.env`;
const envFile = path.resolve(__dirname, '../../', options.folder, filePath);
this.envConfig = dotenv.parse(fs.readFileSync(envFile));
}
get(key: string): string {
return this.envConfig[key];
}
}
정리하자면, register는 동적모듈을 제공하기위해 존재합니다.
물론 직접 선언하고 관리하기에 좀 다르게 할 수는 있지만
적어도 동적모듈을 제공하기 위해서는 forRoot() 또는 register()라는 컨벤션을 추천합니다.
협업을 위해서요 :D
이는 자체 주입토큰이 있는 동적 공급자를 생성하기 위해서 사용합니다.
위의 forRoot, register와는 다르게 말이죠
예를들어 TypeOrm을 봅시다.
이 친구는 forRoot로 커넥션을 생성한 이후, 각 모듈에 사용하는 엔터티들을 각각 지정해주어야 합니다.
다음처럼요
@Module({
imports: [TypeOrmModule.forFeature([Photo])],
providers: [PhotoService],
controllers: [PhotoController],
})
export class PhotoModule {}
PhotoModule에서는 Photo라는 엔터티를 사용하겠다는 의미입니다.
그리고 이 덕분에 레포지토리를 DI 할 수 있어집니다.
@Injectable()
export class PhotoService {
constructor(
@InjectRepository(Photo)
private readonly photoRepository: Repository<Photo>,
) {}
findAll(): Promise<Photo[]> {
return this.photoRepository.find();
}
}
만약 다음과 같은 커스텀 레포지토리를 사용한다면
imports에 repository를 가져와야합니다.
@EntityRepository(Author)
export class AuthorRepository extends Repository<Author> {}
@Module({
imports: [TypeOrmModule.forFeature([AuthorRepository])],
controller: [AuthorController],
providers: [AuthorService],
})
export class AuthorModule {}
@Injectable()
export class AuthorService {
constructor(private readonly authorRepository: AuthorRepository) {}
}
가끔 useFactory와 같이 Module을 설정 할 때 튀어나오는 친구들이 있습니다.
예를 들어 다음과 같이 TypeOrm 옵션을 비동기적으로 전달하는 경우입니다.
TypeOrmModule.forRootAsync({
useFactory: () => ({
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}),
});
그리고 비동기이거나 DI가 될 수도 있습니다.
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => config.get('database'),
inject: [ConfigService],
})
그리고 클래스를 집어넣기도 합니다
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useClass: TypeOrmConfigService,
inject: [ConfigService],
}),
이 경우 TypeOrmConfigService의 객체를 생성하고, 그를 이용하여 옵션개체를 생성합니다.
@Injectable()
class TypeOrmConfigService implements TypeOrmOptionsFactory {
createTypeOrmOptions(config: ConfigService): TypeOrmModuleOptions {
return config.get('database');
}
}
왜 하필 createTypeOrmOptions가 메서드명이냐 를 찾아보니
useClass는 다음과 같은 형태의 값을 취하며
useClass : Function {
new (...args: any[]): TypeOrmOptionsFactory;
}
그러기에 생성자가 TypeOrmOptionsFactory를 리턴해야만 합니다.
그리고 TypeOrmOptionsFactory는 다음과 같습니다.
interface TypeOrmOptionsFactory {
createTypeOrmOptions(connectionName?: string): Promise<TypeOrmModuleOptions> | TypeOrmModuleOptions;
}
고로 implements TypeOrmOptionsFactory를 하여 createTypeOrmOptions를 구현해야 하는 것 입니다.
그리고 TypeOrmModuleOptions는 저희가 위에서 그토록 보던
{
type: 'mysql',
host: 'localhost',
port: 3306,
username: 'root',
password: 'root',
database: 'test',
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true,
}
다음과 같은 값들이 포함되어 있습니다.
어으 길다
useClass와 useFactory에 관해 더 자세한건 Provider에서 계속..!