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!