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
에서 계속..!