@buka/nestjs-config v2.3.2
@buka/nestjs-config
This is an easy-to-use nestjs config module with many surprising features.
Feature
- Config verification by
class-validator - Config transform by
class-transformer - Load configuration files from anywhere
- Perfect coding tips
- Automatically handle naming styles
- Injectable config class
Install
npm install @buka/nestjs-config
yarn install @buka/nestjs-config
pnpm install @buka/nestjs-configUsage
@buka/nestjs-config load config from process.env and .env(local process.cwd()) by defaulted. let us create .env first:
# .env
CACHE_DIR="./tmp"
BROKERS="test01.test.com,test02.test.com,test03.test.com"Then, define a AppConfig class with the @Configuration() decorator.
// app.config.ts
import { Configuration } from "@buka/nestjs-config";
import { IsString, IsOptional, IsIn, IsIp } from "class-validator";
import { Split } from "@miaooo/class-transformer-split";
@Configuration()
export class AppConfig {
// set default value
@IsIp()
host = "0.0.0.0";
// CACHE_DIR in .env
@IsString()
@IsOptional()
cacheDir?: string;
// process.env.NODE_ENV
@IsIn(["dev", "test", "prod"])
nodeEnv: string;
@Split(",")
brokers: string[];
}
@buka/nestjs-configautomatically convert naming styles. For example:cache_dir、CACHE_DIR、cacheDir、CacheDir、cache-dir、Cache_Dirare considered to be the same config name.
Import ConfigModule in your AppModule:
// app.module.ts
import { Module } from "@nestjs/common";
import { ConfigModule } from "@buka/nestjs-config";
import { AppConfig } from "./app.config";
@Module({
// use process.env and read .env by defaulted
imports: [ConfigModule.register({ isGlobal: true })],
})
export class AppModule {}Inject and use AppConfig in your service:
import { Injectable } from "@nestjs/common";
import { AppConfig } from "./app.config";
@Injectable()
export class AppService {
constructor(private readonly appConfig: AppConfig) {}
}Nested Configuration
Nested configuration is the same as using class-validator and class-transformer:
import { Configuration } from "@buka/nestjs-config";
import { IsString } from "class-validator";
export class SubConfig {
// process.env.{ParentFieldName}__KEY
@IsString()
key: string;
}
@Configuration()
export class AppConfig {
// process.env.SUB_FIRST__KEY
@ValidateNested()
@Type(() => SmsTemplate)
subFirst!: SubConfig;
// process.env.SUB_SECOND__KEY
@ValidateNested()
@Type(() => SmsTemplate)
subSecond!: SubConfig;
}Add more dotenv files
import { Module } from "@nestjs/common";
import {
ConfigModule,
processEnvLoader,
dotenvLoader,
} from "@buka/nestjs-config";
import { AppConfig } from "./app.config";
@Module({
imports: [
ConfigModule.register({
isGlobal: true,
loaders: [
processEnvLoader,
// transform DATABASE__HOST="0.0.0.0"
// to DATABASE = { HOST: "0.0.0.0" }
// transform LOG="true"
// to LOG = true
dotenvLoader(".env", { separator: "__", jsonParse: true }),
dotenvLoader(`.${process.env.NODE_ENV}.env`),
],
}),
],
})
export class AppModule {}Custom config loader
// yaml-config-loader.ts
import { ConfigLoader } from "@buka/nestjs-config";
import { parse } from "yaml";
export async function yamlConfigLoader(filepath: string): ConfigLoader {
return (options: ConfigModuleOptions) => {
if (!existsSync(filepath)) {
if (!options.suppressWarnings) {
Logger.warn(`yaml file not found: ${filepath}`, '@buka/nestjs-config');
}
return {};
}
const content = await readFile(filepath);
return parse(content);
};
}Use yamlConfigLoader:
import { Module } from "@nestjs/common";
import { ConfigModule } from "@buka/nestjs-config";
import { AppConfig } from "./app.config";
import { yamlConfigLoader } from "./yamlConfigLoader";
@Module({
imports: [
ConfigModule.register({
isGlobal: true,
loaders: [yamlConfigLoader("my-yaml-config.yaml")],
}),
],
})
export class AppModule {}Add prefix to all class properties
// mysql.config.ts
import { Configuration } from "@buka/nestjs-config";
import { IsString } from "class-validator";
@Configuration("mysql.master")
export class MysqlConfig {
// process : process.env.MYSQL__MASTER__HOST
// .env : MYSQL__MASTER__HOST
// .json : { mysql: { master: { host: "" } } }
@IsString()
host: string;
}Custom the config name of property
// app.config.ts
import { Configuration, ConfigKey } from "@buka/nestjs-config";
import { IsString } from "class-validator";
@Configuration("mysql.master")
export class MysqlConfig {
// process : process.env.DATABASE_HOST
// .env : DATABASE_HOST
// .json : { databaseHost: "" }
@ConfigKey("DATABASE_HOST")
@IsString()
host: string;
}
@ConfigKey(name)will overwrite the prefix of@Configuration([prefix])
Remove warning logs
import { Module } from "@nestjs/common";
import { ConfigModule } from "@buka/nestjs-config";
import { AppConfig } from "./app.config";
@Module({
imports: [
ConfigModule.register({
isGlobal: true,
suppressWarnings: true,
}),
],
})
export class AppModule {}ConfigModule.inject(ConfigProvider, DynamicModule, dynamicModuleOptions)
Simplify the writing of .forRootAsync/.registerAsync.
// pino.config.ts
@Configuration("pino")
export class PinoConfig implements Pick<Params, "assignResponse"> {
@ToBoolean()
@IsBoolean()
assignResponse?: boolean | undefined;
}
// app.module.ts
@Module({
imports: [
ConfigModule.register({ isGlobal: true }),
ConfigModule.inject(PinoConfig, LoggerModule),
],
})
class AppModule {}If the config class implement options of module .forRootAsync/.registerAsync,
The code will become very beautiful.
And implement is not necessary:
import { Module } from "@nestjs/common";
import { ConfigModule } from "@buka/nestjs-config";
// pino.config.ts
@Configuration("pino")
export class PinoConfig {
@IsIn(["fatal", "error", "warn", "info", "debug", "trace"])
level: string = "info";
}
// app.module.ts
@Module({
imports: [
ConfigModule.register({ isGlobal: true }),
// map .level to .pinoHttp.level
ConfigModule.inject(PinoConfig, LoggerModule, (config) => ({
pinoHttp: { level: config.level },
})),
],
})
class AppModule {}Sometimes, a name property is need by options of .forRootAsync/.registerAsync,
like add multiple database in @nestjs/typeorm.
Another one is
isGlobal
@Module({
imports: [
ConfigModule.register({ isGlobal: true }),
ConfigModule.inject(
TypeOrmConfig,
TypeOrmModule,
{ name: "my-orm" },
(config) => config // config mapping function is optional
),
// this is equal to
TypeOrmModule.forRootAsync({
name: "my-orm",
inject: [TypeOrmConfig],
useFactory: (config: TypeOrmConfig) => config,
}),
],
})
export class AppModule {}Preload Config
Sometimes, we have to get config outside the nestjs lifecycle. ConfigModule.preload(options) is designed for this.
There is an example of MikroORM config file:
// mikro-orm.config.ts
import { ConfigModule } from "@buka/nestjs-config";
import { MySqlDriver, defineConfig } from "@mikro-orm/mysql";
import { MysqlConfig } from "./config/mysql.config";
import { Migrator } from "@mikro-orm/migrations";
import { BadRequestException } from "@nestjs/common";
export default (async function loadConfig() {
// Load MysqlConfig
await ConfigModule.preload();
// Get MysqlConfig Instance
const config = await ConfigModule.get(MysqlConfig);
if (!config) throw new Error("Config Not Founded");
// or
// const config = await ConfigModule.getOrFail(MysqlConfig);
return defineConfig({
...config,
entities: ["dist/**/*.entity.js"],
driver: MySqlDriver,
});
})();The
optionsofConfigModule.preload(options)is theoptionsofConfigModule.register(options)
Loaders
| Name | Description |
|---|---|
processEnvLoader | load from process.env |
dotenvLoader | load .env file by dotenv |
dotenvxLoader | load .env file by @dotenvx/dotenvx |
jsonFileLoader | load json file by JSON.parse |
yamlFileLoader | load yaml file by yaml |
tomlFileLoader | load toml file by smol-toml |
Q&A
Reported every field in my Config class was missing, even though they weren't.
This may be due to target in tsconfig.json is ES2021 or lower. We recommend using ES2022 and above.
But, if you must use ES2021, every property key should add @ConfigKey() decorator (See More):
// app.config.ts
import { Configuration, ConfigKey } from "@buka/nestjs-config";
import { IsIp, IsIn } from "class-validator";
@Configuration()
export class AppConfig {
@ConfigKey()
@IsIp()
host = "0.0.0.0";
@ConfigKey()
@IsIn(["dev", "test", "prod"])
nodeEnv: string;
}Nest could not find YourConfig element.
@buka/nestjs-config will autoload all the config classes injected by service.
However, a config that is not used by any service may not be injected into the nestjs app.
And this will causes you to get this error when attempt to app.get(YourConfig).
One solution is use ConfigModule.get(YourConfig) replace app.get(YourConfig):
await ConfigModule.preload();
const yourConfig = await ConfigModule.get(YourConfig);
// If you do this, you probably want to do something outside of the nestjs runtime.
// do...If you have to inject config class which is not used by any service, you can do it like this:
@Module({
ConfigModule.register({
isGlobal: true,
providers: [YourConfig]
}),
})
class AppModule {}8 months ago
8 months ago
6 months ago
8 months ago
11 months ago
8 months ago
9 months ago
8 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago