Nest.js系列——从零搭建多配置开发环境

前言

前面了解了那么多的相关概念,是时候实践一下了。从新建一个初始化项目框架模版开始实践之前学到的理论知识,从实践的角度把涉及到的知识概念整体串联一下,构建一个多环境开发模版。

起步初始化一个项目

首先确定下node开发环境,这里大家自行安装检查。安装全局nestcli工具用来初始化一个项目

安装cli

npm i -g @nestjs/cli

cli创建项目

nest new project-name
.
├── README.md
├── nest-cli.json
├── package.json
├── pnpm-lock.yaml
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   └── main.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json
└── tsconfig.json

配置多环境开发

首先安装配置依赖包@nestjs/config

npm i --save @nestjs/config

@nestjs/config 内部使用 dotenv 实现。

配置

使用多配置需要导入ConfigModule模块,一般会在根模块AppModal中导入,并使用.forRoot()静态方法导入它的配置

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
​
@Module({
  imports: [ConfigModule.forRoot()],
})
export class AppModule {}
​

使用env方式配置

以上配置会默认载入并解析一个.env文件,从.env文件和process.env合并环境变量键值对,并将结果存储到一个可以通过ConfigService访问的私有结构。forRoot()方法注册了ConfigService提供者,后者提供了一个get()方法来读取这些解析/合并的配置变量。由于@nestjs/config依赖dotenv,它使用该包的规则来处理冲突的环境变量名称。当一个键同时作为环境变量(例如,通过操作系统终端如export DATABASE_USER=test导出)存在于运行环境中以及.env文件中时,以运行环境变量优先。

  • .env
DATABASE_USER=root
DATABASE_PASSWORD=test
  • 自定义env文件路径

默认情况下,程序在应用程序的根目录中查找.env文件。 要为.env文件指定另一个路径,请配置forRoot()的配置对象 envFilePath 属性(可选)

ConfigModule.forRoot({
  envFilePath: '.development.env',
});

也可以指定多个文件路径

ConfigModule.forRoot({
  envFilePath: ['.env.development.local', '.env.development'],
});

如果在多个文件中发现同一个变量,则第一个变量优先。

  • 禁止加载环境变量
ConfigModule.forRoot({
  ignoreEnvFile: true,
});
  • 全局使用配置

当您想在其他模块中使用ConfigModule时,需要将其导入(这是任何 Nest 模块的标准配置)。 或者,通过将options对象的isGlobal属性设置为true,将其声明为全局模块,如下所示。 在这种情况下,将ConfigModule加载到根模块(例如AppModule)后,您无需在其他模块中导入它。

ConfigModule.forRoot({
  isGlobal: true,
});

env的环境配置的好处就是简单清晰,而且支持自动载入配置,但是如果配置有很多并且有嵌套的话,这个方式就不太好了,接下来介绍yaml格式的配置

使用yaml方式配置

安装js-yaml包和类型

npm i js-yaml -S
​
npm i @types/js-yaml -D

src文件夹下创建一个config目录来放各个环境的配置,这里创建3个环境dev/prod/test

── src
│   ├── app.module.ts
│   ├── config
│   │   ├── dev.yml
│   │   ├── index.ts
│   │   ├── prod.yml
│   │   └── test.yml
│   ├── main.ts
│   └── user
│       ├── dto
│       │   ├── create-user.dto.ts
│       │   └── update-user.dto.ts
│       ├── entities
│       │   └── user.entity.ts
│       ├── user.controller.ts
│       ├── user.module.ts
│       └── user.service.ts
├── tsconfig.build.json
└── tsconfig.json

config/index.ts是动态加载所有配置的入口页

import { readFileSync } from 'fs';
import * as yaml from 'js-yaml';
import { join } from 'path';
​
const configFileNameObj = {
  development: 'dev',
  test: 'test',
  production: 'prod',
};
​
const env = process.env.NODE_ENV;
​
export default () => {
  return yaml.load(
    readFileSync(join(__dirname, `./${configFileNameObj[env]}.yml`), 'utf8'),
  ) as Record<string, any>;
};

这里有个需要注意的地方导入js-yaml的时候一定要用* as yaml或者require(),不然的话会报错load方法不存在,当然你也可以单独导入方法,看你喜好。

还需要注意的是导出必须是个函数,以为配置包的load方法只接受函数数组

yml格式的配置文件比如dev.yml

# 数据库配置
db:
  mysql:
    host: 'localhost'
    username: 'root'
    password: '123456'
    database: 'kapok'
    port: 3306
    charser: 'utf8mb4'
    logger: 'advanced-console'
    logging: true
    multipleStatements: true
    dropSchema: false
    synchronize: true
    supportBigNumbers: true
    bigNumberStrings: true

然后在app.module.ts中配置导入yml配置

import configuration from './config/index';
​
@Module({
  imports: [
    ConfigModule.forRoot({
      cache: true,
      load: [configuration],
      isGlobal: true,
    }),
  ],
  controllers: [],
  providers: [],
})

现在如果启动项目的话,大概率yml文件会找不到而报错,此时还需要去配置下cli的配置文件nest-cli.json

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "assets": ["**/*.yml"],
    "deleteOutDir": true,
    "watchAssets": true
  },
  "generateOptions": {
    "spec": false
  }
}

特别注意的就是这个打包配置compilerOptions中的assets,这个要配置成所有的yml文件,这样才能在dist中找到配置的配置文件

dist
│   ├── app.module.d.ts
│   ├── app.module.js
│   ├── app.module.js.map
│   ├── config
│   │   ├── dev.yml
│   │   ├── index.d.ts
│   │   ├── index.js
│   │   ├── index.js.map
│   │   ├── prod.yml
│   │   └── test.yml

接下来还需要去修改一下npmscript命令,加上环境变量来运行对应的环境配置,先安装一个cross-env

npm i cross-env -D

然后修改对应环境的脚本命令

 "start:dev": "cross-env NODE_ENV=development nest start --watch",
 "start:debug": "cross-env NODE_ENV=development nest start --debug --watch",
 "start:test": "cross-env NODE_ENV=test node dist/main",
 "start:prod": "cross-env NODE_ENV=production node dist/main",

启动项目的时候就能加载对应的环境配置了。

使用配置

使用配置的话,需要导入@nestjs/config提供的服务来获取配置信息,比如在user模块使用配置,因为配置已经设置为全局可用,所以不再需要在user模块中导入了,可以直接使用

import { ConfigService } from '@nestjs/config';
​
@Controller('user')
export class UserController {
  constructor(
    private readonly userService: UserService,
    private configService: ConfigService,
  ) {}
​
  @Get()
  findAll() {
    console.log('configService===', this.configService.get('db'));
​
    return this.userService.findAll();
  }

这里能打印出对应的配置说明多配置环境可以使用了,这是在控制器里面使用这个配置服务,通过注入的方式,那如果是想在模块文件中使用配置服务怎么办,就拿数据库的配置来举例

@Module({
  imports: [
    // 数据库
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => {
        return {
          type: 'mysql',
          // 可能不再支持这种方式,entities 将改成接收 实体类的引用
          //
          // entities: [`${__dirname}/**/*.entity{.ts,.js}`],
          autoLoadEntities: true,
          keepConnectionAlive: true,
          ...config.get('db.mysql'),
          // cache: {
          //   type: 'ioredis',
          //   ...config.get('redis'),
          //   alwaysEnabled: true,
          //   duration: 3 * 1000, // 缓存3s
          // },
        } as TypeOrmModuleOptions;
      },
    }),
  ],
  controllers: [],
  providers: [],
})

由配置的例子可以看出通过一个useFactory函数,然后将配置服务作为参数传递进行使用,就可以在模块文件中使用配置信息了

Joi

虽然对配置做了配置文件的抽离,但是配置写的对不对并不能过判断出来,这就需要一个校验的工具,这个工具是Joi,通过这个包可以对配置信息作校验,如果有问题会抛出错误,及时检验。

首先安装joi

npm i joi -S

假设去校验开发环境的变量配置和服务启动的端口,可以在配置服务的地方这样做

 ConfigModule.forRoot({
      cache: true,
      load: [configuration],
      isGlobal: true,
      validationSchema: Joi.object({
        NODE_ENV: Joi.string()
          .valid('development', 'production', 'test')
          .default('development'),
        PORT: Joi.number().default(3000),
      }),
      validationOptions: {
        // 控制是否允许环境变量中未知的键 默认为true
        allowUnknown: true,
        // 在遇到第一个错误时就停止验证,如果为false就返回所有错误,默认为false
        abortEarly: true,
      },
    }),

这个简单的校验就完成了,通过配置模块提供的配置,很轻松的就接入了配置的校验,这里有个需要注意的地方,引入joi的时候如果是使用esm的方式就需要全部重命名导入

import * as Joi from 'joi'

不然是会报错方法找不到的,和js-yaml一样的问题,因为没有默认导出全部

小结

到这里基本是搭建好了一个多环境开发的基础项目,还有一些关于开发的接口方面的配置比如jwt统一封装返回体统一拦截报错等,下次在整理吧,希望对你有帮助!!!

原文链接:https://juejin.cn/post/7216536069285707813 作者:water

(1)
上一篇 2023年4月3日 下午4:22
下一篇 2023年4月3日 下午4:32

相关推荐

发表回复

登录后才能评论