After creating this proposal I decided to create this prototype before attempting to modify the existing code base. This also enables me to create a fresh scenario to test my logic and new implementation of features etc.
$ yarn test
This prototype will hopefully enable a better solution to using the nestjs-config module and be more of a 'nest' implementation of the existing module and utilise more of the nestjs container instead of 'bunging' everyrthing into one provider thus providing a better usage of nestjs and the config module.
Injecting will move away from a singular module inject and enable more freely injectable providers as such
Basic objects can be injected using a token
// config/my_config.ts
export default {
test: string = 'hello';
}
import {InjectConfig} from 'nestjs-config';
import {Controller} from '@nestjs/common';
@Controller()
export class ExampleController {
constructor(private readonly @InjectConfig('my_config') config) {}
someMethod(): string {
return this.config.test;
}
anotherMethod(): string {
return this.config.get<string>('not.defined', 'default value');
}
}
In the above example you'll notice that there is a method get
on the injected config parameter. That's because all object configs are 'wrapped' in a Config
class which provides such lodash get functionality like in the previous version of nestjs-config
. Except now it has a Type Parameter (Thanks @natc ;)).
Typed classes etc can be used to inject your config.
In this example the class used does not come with the get
functionality although you could extend the config class if you so wished export default class MyConfigClass extends Config {}
.
All 'types' are used as ClassProvider
s. So they are technically injectables?
// config/my.config.class.ts
export default class MyConfigClass {
public static test: string = 'hello';
}
@Module({
imports: [
ConfigModule.forRootAsync(path.resolve(__dirname, 'config', '**', '*(!.d).{ts,js}')),
SomeModle.forRootAsync({
useFactory: (conifg: MyConfigClass) => config,
inject: [MyConfigClass],
}),
],
})
export class ExampleModule {}
The below will not work as ConfigProviders are created asynchronously via the ConfigModule.
import { MyConfigClass } from './config/my.config.class';
import { Injectable } from '@nestjs/common';
@Injectable()
export class ExampleProvider {
constructor(private readonly config: MyConfigClass) {}
}
The token used can be manipulated as such
// config/my.defined.provide.ts
export default {
__provide: 'my_custom_token',
};
import {Injectable} from '@nestjs/common';
Injectable()
export class ExampleProvider {
constructor(private readonly @InjectConfig('my_custom_token') config) {}
}
Again the above should work with Provider types such as
// config/database.ts
export default class DatabaseConfig implements TypeOrmModuleOptions {
public static type: string = 'mysql';
public static host: process.env.TYPEORM_HOST,
public static port: process.env.TYPEORM_PORT,
public static username: process.env.TYPEORM_USERNAME,
public static password: process.env.TYPEORM_PASSWORD,
}
import { Module } from '@nestjs/common';
import { ConfigModule } from 'nestjs-config';
import { TypeOrmModule } from '@nestjs/typeorm';
import DatabaseConfig from './config/database';
@Module({
imports: [
ConfigModule.forRootAsync('config/**/*(!.d).{ts,js}'),
TypeOrmModule.forRootAsync({
useFactory: (config: DatabaseConfig) => config,
injects: [DatabaseConfig],
}),
],
})
export class ExampleModule {}
Alternatively using no class/type
// config/database.ts
export default {
type: 'mysql',
host: process.env.TYPEORM_HOST,
...
}
import { Module } from '@nestjs/common';
import { ConfigModule, configToken } from 'nestjs-config';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
ConfigModule.forRootAsync('config/**/*(!.d).{ts,js}'),
TypeOrmModule.forRootAsync({
useFactory: config => config,
injects: [configToken('database')],
}),
],
})
export class ExampleModule {}
By default the file name is used for the token if no __provide
key is specified.
In my proposal I wasn't sure if the ConfigService still had a role within the config module. However I've managed to maintain some of the v1 functionality by utiling the moduleRef and keeping local references of the __name
,__provide
or file name of the provider's token being created. I'm not sure I like this functionality as it is; as a provider currently cannot be found using a ClassProvider token which is kinda sad. Would be nice but not quite sure how it would really work? Plus using the moduleRef seems a little hacky and kind of defeats the purpose of types before runtime.
import {Injectable} from '@nestjs/common';
import {ConfigService} from 'nestjs-config';
@Injectable()
export class ExampleProvider {
constructor(private readonly configService: ConfigService) {}
someMethod(): string {
return this.configService.get<string>('my_config.test', 'something that doesn\'t say hello');
}
}
Merge has been remove in v2, the prefered method is to use ConfigModule.forRootAsync
in your module.
The renaming method has been removed as of v2. The perfered method now is to define your name/provide token using the __name
or __provide
keys defined in DefinedConfigProvider
.
- forRoot method
-
merging for modules(Might as well use comfigModule.forRootAsync?) - dotenv loading
- resolveRootPath
- documentation comments
- throw exception on no default export from file? Or consider what to do if multiple exports
- throw exception on no defined config provider
- throw exception when no reference token is found
- validating config types etc with different package/drivers
- add configService back into configModule and resolve names with references of their token
-
add rename method etc (do I still need this with __name || __provide?) - add configModuleOptions back in
-
attempt static property setting with decoratordon't need it -
attempt better parameter decorator setterdon't need it - consider replacing loadash.get with ts-get
-
make sure injection via type works correctlyWon't work because the token doesn't exist before injectable is instanced - make a 'toString' or 'toObject' method for when calling as
config
as the tokens would requireuseFactory: (config: Config) => config.config)
bit annoying. Maybe just define to Config instead of Config.config