preface
When writing the back end, we generally advocate configuration file separation. Env can be used to maintain our environment variables, encapsulate the corresponding factory functions and combine more complex configurations! For example, we use image (Docker), can map configuration file directory externally; To meet the needs of different environments using differentiated configuration! (Runtime loading is allowed!) Other than that, you can look at my configuration separation ideas ~~
In actual combat
The installation
- Nestjs /config: Nest configuration center based on Dotenv package
- Joi: a very flexible schema validation tool
- @types/hapi__joi: typescript declarations of joi
# @nestjs/config has dotenv built in
yarn add @nestjs/config joi
yarn add -D @types/hapi__joi
Copy the code
Basic Configuration
@nestjs/config option explanation
Annotate it directly with typescript
export interfaceConfigModuleOptions { isGlobal? :boolean; // Enabling this applies to the entire system (global module), not just the module you are currently injecting!ignoreEnvFile? :boolean; Env files are ignored if this is enabled.ignoreEnvVars? :boolean; // Ignore system-level variable injection, default is off (read system variables)envFilePath? :string | string[];// env file, based on the runtime root path (process.cwd)encoding? :string; // File encoding, recommended utF-8, high fault tolerance!validationSchema? :any; // Can verify all incoming custom environment variables (system variables will be appended if not closed)validationOptions? : Record<string.any>; load? :Array<ConfigFactory>; // Factory functions that load environment variables can be used to combine complex configurationsexpandVariables? :boolean; // Support environment variables nested variables,
}
Copy the code
{
"validationOptions": {
"allowUnknown": false.// Controls whether unknown keys in environment variables are allowed. The default is true.
"abortEarly": true.// If true, stop validation on the first error; If false, all errors are returned. The default is false.}}Copy the code
APP_VERSION=${APP_NAME} -v1/ / github.com/motdotla/dotenv-expand
"expandVariables":true
}
Copy the code
Application in project
I prefer to put all environment variable configurations in the root directory config. What good is that? Configuration is centralized and mapping is easy (for example with Docker). Separate environments (development, testing, production!)
├─.Eslintrc.js ├─.GetTignore ├─.Prettierrc ├─ config# here│ └ ─ ─ env │ ├ ─ ─ dev. Local. The env │ ├ ─ ─ HTTP. Env │ └ ─ ─ report. The env ├ ─ ─ nest - cli. Json ├ ─ ─ package. The json ├ ─ ─ the SRC │ ├ ─ ─ │ ├─ App.controll.spec. │ ├─ app.controll.spec. │ ├─ app.module. │ ├─ config │ │ ├ ─ ─ env │ │ ├ ─ ─ HTTP status - code. The MSG. Ts │ │ └ ─ ─ the module │ ├ ─ ─ main. Ts │ └ ─ ─ utils │ ├ ─ ─ apm - init. Ts │ ├ ─ ─ ├─ ├─ get-dir-all-file-name- ├─ get-dir-all-file-name- ├─ ├─test│ ├ ─ ─ app. E2e - spec. Ts │ └ ─ ─ jest - e2e. Json ├ ─ ─ tsconfig. Build. The json ├ ─ ─ tsconfig. Json ├ ─ ─ yarn - error. The log └ ─ ─ yarn. The lockCopy the code
Let’s say I pulled out swagger, AXIos
Initial Configuration (app.module)
import * as Joi from '@hapi/joi';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import envReportConfig from './config/env/report.config';
import envSwaggerConfig from './config/env/swagger.config';
import { getDirAllFileNameArr } from './utils/get-dir-all-file-name-arr';
@Module({
imports: [
ConfigModule.forRoot({
encoding: 'utf-8'.envFilePath: [...getDirAllFileNameArr()],
expandVariables: true.// Enable nested variables
ignoreEnvVars: true.load: [envReportConfig, envSwaggerConfig],
validationSchema: Joi.object({
H3_APM_SERVER_URL: Joi.string().default(' '),
H3_LATEINOS_REPORT_URL: Joi.string().default(' '),
SERVE_LISTENER_PORT: Joi.number().default(3000),
SWAGGER_SETUP_PATH: Joi.string().default('api-docs'),
SWAGGER_ENDPOINT_PREFIX: Joi.string().default('api/v1'),
SWAGGER_UI_TITLE: Joi.string().default('Swagger Document title '),
SWAGGER_UI_TITLE_DESC: Joi.string().default('Hurry up and change the relevant configuration ~~'),
SWAGGER_API_VERSION: Joi.string().default('1.0'),
HTTP_TIMEOUT: Joi.number().default(5000),
HTTP_MAX_REDIRECTS: Joi.number().default(5),
NODE_ENV: Joi.string()
.valid('development'.'production'.'test'.'provision')
.default('development'),}),validationOptions: {
allowUnknown: false.// Controls whether unknown keys in environment variables are allowed. The default is true.
abortEarly: true.// If true, stop validation on the first error; If false, all errors are returned. The default is false.}}),].controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Copy the code
Here are four things I can say
Joi
Here we use only the basic joI syntax, such as default conversion formats and adding default values.
Matches array valid values. If not, use default values ~
A similar error is thrown if an environment variable uses an exception, or if a transformation exception is used
The load of ConfigModule
This can be used to load a combination of configuration functions, such as if you have some configuration scattered in multiple. Env, and then need to assemble an object passed in, easy to use! Examples in specific projects, first defined!
// env configuration
import { registerAs } from '@nestjs/config';
export interface EnvSwaggerOptions {
title: string;
setupUrl: string; desc? :string;
prefix: string;
version: string;
}
export default registerAs(
'EnvSwaggerOptions', () :EnvSwaggerOptions= > ({
title: process.env.SWAGGER_UI_TITLE,
desc: process.env.SWAGGER_UI_TITLE_DESC,
version: process.env.SWAGGER_API_VERSION,
setupUrl: process.env.SWAGGER_SETUP_PATH,
prefix: process.env.SWAGGER_ENDPOINT_PREFIX,
}),
);
Copy the code
And then use it, like we use it in the main entrance of the project!
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import { EnvSwaggerOptions } from './config/env/swagger.config';
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from './common/pipes/validataion.pipe';
import { terminalHelpTextConsole } from './utils/terminal-help-text-console';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
cors: false.logger: false});// app.get can get the corresponding successful initialization instance!
const configService = app.get(ConfigService);
// configService.get can get the configuration object or system variable we encapsulated!
const swaggerOptions = configService.get<EnvSwaggerOptions>(
'EnvSwaggerOptions',);const options = new DocumentBuilder()
.setTitle(swaggerOptions.title)
.setDescription(swaggerOptions.desc)
.setVersion(swaggerOptions.version)
.addBearerAuth()
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup(swaggerOptions.setupUrl, app, document, {
customSiteTitle: swaggerOptions.title,
swaggerOptions: {
docExpansion: 'list'.filter: true.showRequestDuration: true,}});await app.listen(configService.get('SERVE_LISTENER_PORT'));
}
bootstrap().then(() = > {
// This thing is its own patchwork, after the success of the start of the terminal output point of the dongdong implementation and the effect picture is as follows
terminalHelpTextConsole();
});
Copy the code
import * as chalk from 'chalk';
type paramType = {
Port: string | number;
DocUrl: string;
ApiPrefix: string;
};
const defaultParam: paramType = {
Port: process.env.SERVE_LISTENER_PORT,
DocUrl: process.env.SWAGGER_SETUP_PATH,
ApiPrefix: process.env.SWAGGER_ENDPOINT_PREFIX,
};
/** * Print related help information to terminal *@param params* /
export function terminalHelpTextConsole(params = defaultParam) :void {
const Host = `http://localhost`;
console.log(
chalk.red.bold('Swagger Document Link :'.padStart(16)),
chalk.green.underline(`${Host}:${params.Port}/${params.DocUrl}`));console.log(
chalk.red.bold(Restful apis.padStart(16)),
chalk.green.underline(`${Host}:${params.Port}/${params.ApiPrefix}`)); }Copy the code
The ConfigModule envPath
I don’t like to have to manually maintain what could be an increasing number of configuration files, so I wrote a function that takes the first level of file names at once and concatenates them into an array; Determines whether it is a file with the suffix. Env
// get-dir-all-file-name-arr.ts
import * as fs from 'fs';
import * as path from 'path';
// Default directory where env files are stored
const directory = path.resolve(process.cwd(), 'config/env');
typeoptionsType = { dirPath? :string; prefix? :string;
};
/** * Returns the names of all files in the directory (as an array of strings) *@typedef {Object} Options Parameter options *@param {string} Options. dirPath Directory path *@param {string} Options. prefix prefixes each match with text *@return {string[]} By default, /config/env returns an array */ for all files
export function getDirAllFileNameArr(options? : optionsType) :string[] {
const params = { dirPath: directory, prefix: 'config/env/'. options };const results = [];
try {
for (const dirContent of fs.readdirSync(params.dirPath)) {
const dirContentPath = path.resolve(directory, dirContent);
console.log(dirContentPath);
if (fs.statSync(dirContentPath).isFile()) {
if (dirContent.endsWith('.env')) {
if (params.prefix) {
results.push(`${params.prefix}${dirContent}`);
} else{ results.push(dirContent); }}}}return results;
} catch (error) {
returnresults; }}// output
/** * [ * 'config/env/dev.local.env', * 'config/env/http.env', * 'config/env/report.env' * ] */
Copy the code
Environment variable Output
This can be seen by printing process.env in main.js bootstrap
conclusion
At this point, a good maintenance posture has been put in place; If there is something wrong, please leave a message and correct it in time. Thank you for reading!