网纹图解
不再写字符串来读取配置 .
特征
- 提供一个安全且直观的配置方式 鸟巢 工程,和请求的 DTO 一样平滑 .
- 用环境变量、json/yaml/toml配置文件或远程端点来装入您的配置 .
- 验证您的配置 类验证器 财务报告和审定财务报表 类转换器。 。 。 .
- 默认情况下提供方便使用选项, 而所有选项都是自定义的 .
安装
$ npm i --save nest-typed-config
Nest-typed-config 将默认安装所有加载者的依赖性。 如果你关心依赖大小和靴子时间,请退房 跳过可选依赖的指南。 。 。 .
灵感
有各种流行的配置模块 巢穴框架,例如 正式配置模块, (中文(简体) ), 巢状配置 财务报告和审定财务报表 巢状- 容易配置。这些模块可以帮助管理配置,验证它们,并通过 ConfigService但正是这时候 类型的安全就消失了。 例如:
// @nestjs/config, with type-casting
const dbUser = this.configService.get<string>('DATABASE_USER');
// nestjs-config, returns `any` type
const env = this.config.get('app.environment');
// nestjs-easyconfig, only string is supported
const value = this.config.get('key');
写作类型铸造是一种痛苦和难以维护,在现实世界的项目中常用非字符串配置. 该模块旨在提供直观和类型安全的方式来加载,验证和使用配置. 只要导入任何配置模型,并注入完整的TypeScript支持. 简言之:
// config.ts
export class Config {
@IsString()
public readonly host!: string;
@IsNumber()
public readonly port!: number;
}
// app.service.ts
import { Config } from './config';
@Injectable()
export class AppService {
constructor(private readonly config: Config) {}
show() {
console.log(`http://${this.config.host}:${this.config.port}`);
}
}
快速启动
先定义配置模型. 其巢可被任意地入入地.
// config.ts
import { Allow, ValidateNested } from 'class-validator';
// validator is omitted for simplicity
export class TableConfig {
@Allow()
public readonly name!: string;
}
export class DatabaseConfig {
@Type(() => TableConfig)
@ValidateNested()
public readonly table!: TableConfig;
}
export class RootConfig {
@Type(() => DatabaseConfig)
@ValidateNested()
public readonly database!: DatabaseConfig;
}
然后,添加一个配置文件,例如 .env.yaml 在项目根目录中 :
database:
table:
name: example
创建配置文件后导入 TypedConfigModule 财务报告和审定财务报表 fileLoader 从文件系统装入配置.
// app.module.ts
import { Module } from '@nestjs/common';
import { TypedConfigModule, fileLoader } from 'nest-typed-config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { RootConfig } from './config';
// Register TypedConfigModule
@Module({
imports: [
TypedConfigModule.forRoot({
schema: RootConfig,
load: fileLoader(),
}),
],
providers: [AppService],
controllers: [AppController],
})
export class AppModule {}
就这样! 您可以使用任何您定义的可注射服务配置或子配置 !
// app.service.ts
import { Injectable } from '@nestjs/common';
import { RootConfig, DatabaseConfig, TableConfig } from './config';
@Injectable()
export class AppService {
// inject any config or sub-config you like
constructor(
private config: RootConfig,
private databaseConfig: DatabaseConfig,
private tableConfig: TableConfig,
) {}
// enjoy type safety!
public show(): any {
const out = [
`root.name: ${this.config.name}`,
`root.database.name: ${this.databaseConfig.name}`,
`root.database.table.name: ${this.tableConfig.name}`,
].join('\n');
return `${out}\n`;
}
}
使用装载器
使用 dotenv 加载器
这个 dotenvLoader 函数允许您加载配置 多腾v,这类似于 正式配置模块。 您可以使用此加载器从 .env 文件或环境变量.
示例
TypedConfigModule.forRoot({
schema: RootConfig,
load: dotenvLoader({
/* options */
}),
});
通过选项
这个 dotenvLoader 函数可选择预期 a DotenvLoaderOptions 作为第一个参数的对象 :
export interface DotenvLoaderOptions {
/**
* If set, use the separator to parse environment variables to objects.
*
* @example
*
* ```bash
* app__port=8080
* db__host=127.0.0.1
* db__port=3000
* ```
*
* if `separator` is set to `__`, environment variables above will be parsed as:
*
* ```json
* {
* "app": {
* "port": 8080
* },
* "db": {
* "host": "127.0.0.1",
* "port": 3000
* }
* }
* ```
*/
separator?: string;
/**
* If set, this function will transform all environment variable keys prior to parsing.
*
* Be aware: If you transform multiple keys to the same value only one will remain!
*
* @example
*
* .env file: `PORT=8080` and `keyTransformer: key => key.toLowerCase()` results in `{"port": 8080}`
*
* @param key environment variable key
*/
keyTransformer?: (key: string) => string;
/**
* If "true", environment files (`.env`) will be ignored.
*/
ignoreEnvFile?: boolean;
/**
* If "true", predefined environment variables will not be validated.
*/
ignoreEnvVars?: boolean;
/**
* Path to the environment file(s) to be loaded.
*/
envFilePath?: string | string[];
/**
* A boolean value indicating the use of expanded variables.
* If .env contains expanded variables, they'll only be parsed if
* this property is set to true.
*
* Internally, dotenv-expand is used to expand variables.
*/
expandVariables?: boolean;
}
使用文件加载器
这个 fileLoader 函数允许您加载配置 宇宙图您可以使用此加载器从带有各种扩展名的文件装入配置,例如 .json, (中文(简体) ), .yaml, (中文(简体) ), .toml 或者说 .js。 。 。 .
默认, fileLoader 搜索 .env.{ext} (ext = json, yaml, toml, js) 配置文件开始于 process.cwd(),并继续搜索目录树,直到它找到一些可接受的配置(或点击主目录). 此外,当前环境的配置优先于一般配置(.env.development.toml 正在装入而不是 .env.toml 何时 NODE_ENV=development(中文(简体) )
示例
TypedConfigModule.forRoot({
schema: RootConfig,
load: fileLoader({
/* options */
}),
});
通过选项
这个 fileLoader 函数可选择预期 a FileLoaderOptions 作为第一个参数的对象 :
import { OptionsSync } from 'cosmiconfig';
export interface FileLoaderOptions extends Partial<OptionsSync> {
/**
* basename of config file, defaults to `.env`.
*
* In other words, `.env.yaml`, `.env.yml`, `.env.json`, `.env.toml`, `.env.js`
* will be searched by default.
*/
basename?: string;
/**
* Use given file directly, instead of recursively searching in directory tree.
*/
absolutePath?: string;
/**
* The directory to search from, defaults to `process.cwd()`. See: https://github.com/davidtheclark/cosmiconfig#explorersearch
*/
searchFrom?: string;
/**
* If "true", ignore environment variable substitution.
* Default: true
*/
ignoreEnvironmentVariableSubstitution?: boolean;
}
如果您想要为其他扩展添加支持, 您可以使用 loaders 提供的财产 cosmiconfig数字 :
TypedConfigModule.forRoot({
schema: RootConfig,
load: fileLoader({
// .env.ini has the highest priority now
loaders: {
'.ini': iniLoader,
},
}),
});
使用目录加载器
这个 directoryLoader 函数允许您在给定目录中装入配置.
文件的基名将解释为配置名空间,例如:
.
└─config
├── app.toml
└── db.toml
// app.toml
foo = 1
// db.toml
bar = 1
上述文件夹将生成如下配置 :
{
"app": {
"foo": 1
},
"db": {
"bar": 1
}
}
示例
TypedConfigModule.forRoot({
schema: RootConfig,
load: directoryLoader({
directory: '/absolute/path/to/config/directory',
/* other cosmiconfig options */
}),
});
通过选项
这个 directoryLoader 函数可选择预期 a DirectoryLoaderOptions 作为第一个参数的对象 :
import { OptionsSync } from 'cosmiconfig';
export interface DirectoryLoaderOptions extends OptionsSync {
/**
* The directory containing all configuration files.
*/
directory: string;
/**
* File regex to include.
*/
include?: RegExp;
/**
* If "true", ignore environment variable substitution.
* Default: true
*/
ignoreEnvironmentVariableSubstitution?: boolean;
/**
* If "true", disallow undefined environment variables.
* Default: true
*/
disallowUndefinedEnvironmentVariables?: boolean;
}
如果您想要为其他扩展添加支持, 您可以使用 loaders 提供的财产 cosmiconfig数字 :
TypedConfigModule.forRoot({
schema: RootConfig,
load: directoryLoader({
directory: '/path/to/configuration',
// .env.ini has the highest priority now
loaders: {
'.ini': iniLoader,
},
}),
});
使用远程加载器
这个 remoteLoader 函数允许您从远程端点加载配置,例如配置中心。 内部 @nestjs/ axios (英语) 用于执行 http 请求.
示例
// forRootAsync should be used when loading configuration asynchronously
TypedConfigModule.forRootAsync({
schema: RootConfig,
load: remoteLoader('http://localhost:8080', {
/* options */
}),
});
通过选项
这个 remoteLoader 函数可选择预期 a RemoteLoaderOptions 对象作为第二个参数,它接受所有 axios 请求配置除 url。 。 。 .
export interface RemoteLoaderOptions extends AxiosRequestConfigWithoutUrl {
/**
* Config file type
*/
type?: ((response: any) => RemoteLoaderConfigType) | RemoteLoaderConfigType;
/**
* A function that maps http response body to corresponding config object
*/
mapResponse?: (config: any) => Promise<any> | any;
/**
* A function that determines if the request should be retried
*/
shouldRetry?: (response: AxiosResponse) => boolean;
/**
* Number of retries to perform, defaults to 3
*/
retries?: number;
/**
* Interval in milliseconds between each retry
*/
retryInterval?: number;
}
你可以用这个 mapResponse 功能在解析前预处理服务器响应 type,并使用 shouldRetry 函数来确定服务器响应是否有效。 当服务器响应无效时, 您可以使用 retries 财务报告和审定财务报表 retryInterval 调整重试策略。 例如:
/*
Example server response:
{
"code": 0,
"fileName": ".env.yaml",
"fileType": "yaml",
"fileContent": "database:\n table:\n name: example"
}
*/
TypedConfigModule.forRootAsync({
schema: RootConfig,
load: remoteLoader('http://localhost:8080', {
type: response => response.fileType,
mapResponse: response => response.fileContent
// retry when http status is not 200, or response code is not zero
shouldRetry: response => response.data.code !== 0
retries: 3,
retryInterval: 3000
}),
})
使用多加载器
从文件系统装入配置对于开发来说是方便的,但是在部署的时候,你可能需要从环境变量装入配置,特别是在被打乱的环境中. 这可以通过提供多台装载机实现. 例如:
TypedConfigModule.forRoot({
schema: RootConfig,
// Loaders having larger index take precedence over smaller ones,
// make sure dotenvLoader comes after fileLoader ensures that
// environment variables always have the highest priority
load: [
fileLoader({
/* options */
}),
dotenvLoader({
/* options */
}),
],
});
使用自定义装入器
如果提供本地装载机 nest-typed-config 无法满足您的需要,您可以执行自定义加载器。 可以通过提供一个函数,通过该函数同步或同步返回配置对象 load 选项。 例如:
TypedConfigModule.forRootAsync({
schema: RootConfig,
load: async () => {
return {
host: '127.0.0.1',
port: 3000,
};
},
});
可变替代品的使用
这个 ${PORT} 替换特性允许您使用环境变量 。 您也可以提供默认值,并在配置中引用另一个变量
如果您有类似下面的配置文件
database:
host: 127.0.0.1
port: ${PORT:-12345}
url: ${database.host}:${database:port}
您为端口设置了环境变量
PORT=9000
设置在文件记录选项中忽略了环境可变替换为虚假
load: fileLoader({
ignoreEnvironmentVariableSubstitution: false,
}),
接下来 fileloader 将决定 ${PORT} 占位符并替换为环境变量.
你会得到新的配置 像下一个
database:
host: 127.0.0.1
port: 9000
url: 127.0.0.1:9000
如果您不会为端口设置环境变量, 那么您将会得到像下面一个这样的新配置
database:
host: 127.0.0.1
port: 12345
url: 127.0.0.1:12345
[注] 当您使用可变量替代时,如果您使用默认变量或env变量,则数值可以是字符串,您需要将变压器应用到类字段,以获得数值的正确类型.
export class Config {
@IsString()
public readonly host!: string;
@IsNumber()
@Type(() => Number)
public readonly port!: number;
@IsString()
public readonly url!: string;
}
默认值
在配置图中定义您的默认值, 您准备出发 :
// config.ts
export class Config {
@IsString()
public readonly host: string = '127.0.0.1';
@IsNumber()
public readonly port: number = 3000;
}
转换原始配置
环境变量总是被装入为字符串,但配置方案不是. 在这种情况下,你可以将原始配置转换为 normalize 函数 :
// config.ts
export class Config {
@IsString()
public readonly host: string;
@IsNumber()
public readonly port: number;
}
// app.module.ts
TypedConfigModule.forRoot({
schema: RootConfig,
load: dotenvLoader(),
normalize(config) {
config.port = parseInt(config.port, 10);
return config;
},
});
自定义获取器
您可以定义配置图上的自定义获取器以提取共同逻辑 :
export class Config {
@IsString()
public readonly host: string = '127.0.0.1';
@IsNumber()
public readonly port: number = 3000;
@IsString()
public get url(): string {
return `http://${this.host}:${this.port}`;
}
}
多个外观
打电话 TypedConfigModule.forRoot 多次,你准备走了!
PS:请不要在计划之间共享任何类或子类,否则Nest.js不会知道要注射哪个类.
// config.ts
export class FooConfig {
@IsString()
foo!: string;
}
export class BazConfig {
@IsString()
baz!: string;
}
// app.module.ts
@Module({
imports: [
// load FooConfig from config file
TypedConfigModule.forRoot({
schema: FooConfig,
load: fileLoader(),
}),
// load BazConfig from environment variables
TypedConfigModule.forRoot({
schema: BazConfig,
load: dotenvLoader(),
}),
],
})
export class AppModule {}
自定义验证函数
如果默认 validate 函数不设置您的使用大小写, 您可以像下面的例子那样提供 :
TypedConfigModule.forRoot({
schema: RootConfig,
validate: (rawConfig: any) => {
const config = plainToClass(RootConfig, rawConfig);
const schemaErrors = validateSync(config, {
forbidUnknownValues: true,
whitelist: true,
});
if (schemaErrors.length) {
throw new Error(TypedConfigModule.getConfigErrorMessage(schemaErrors));
}
return config as RootConfig;
},
});
使用 Nest 的 IoC 容器外的配置( 装饰器中的用户)
小心点!
使用 Nest 的 IoC 容器外的配置将:
- 让你更努力的写作测试.
- 强制您注册
TypedConfigModule同步使用forRoot因为在这种情况下同步的配置加载是没有道理的.
怎么样
由于JavaScript加载模块的性质,装饰器在Nest模块初始化之前被执行. 如果你想在装饰器中获得配置值 @Controller() 或者说 @WebSocketGateway(),配置模块应在应用程序拖放前初始化.
假设我们需要从配置中注入路由信息,那么我们可以这样定义配置:
// config.ts
import { Type } from 'class-transformer';
import { ValidateNested, IsString } from 'class-validator';
export class RouteConfig {
@IsString()
public readonly app!: string;
}
export class RootConfig {
@ValidateNested()
@Type(() => RouteConfig)
public readonly route!: RouteConfig;
}
然后创建配置文件 :
route:
app: /app
创建配置文件后, 我们可以初始化 ConfigModule 与 TypedConfigModule选择 RootConfig 从 ConfigModule 使用 selectConfig 方法.
// config.module.ts
import { TypedConfigModule, fileLoader, selectConfig } from 'nest-typed-config';
import { RouteConfig } from './config';
export const ConfigModule = TypedConfigModule.forRoot({
schema: RootConfig,
load: fileLoader(),
});
export const rootConfig = selectConfig(ConfigModule, RootConfig);
export const routeConfig = selectConfig(ConfigModule, RouteConfig);
就这样! 你用得着 rootConfig 财务报告和审定财务报表 routeConfig 现在在你的应用程序里!
如果目标配置模型被标记
@Optional(),你应该打电话selectConfig与{ allowOptional: true }允许可选配置.
// app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { rootConfig } from './config.module';
@Controller(routeConfig.app)
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
show(): void {
return this.appService.show();
}
}
API 密码
请参看我们 API 网站 用于完整文档.
更改日志
请参考 变化日志.md
许可证
麻省理工学院。 。 。 .