nestjs接入动态配置

nestjs动态配置接入的原因

应用程序通常在不同的环境中运行。根据环境的不同,应使用不同的配置设置。例如,通常本地环境依赖于本机的数据库凭证,仅对本地数据库实例有效。生产环境将使用一组单独的数据库凭据。由于配置变量会发生变化,因此最佳实践是在环境中存储配置变量。

如何安装配置

1
$ npm i --save @nestjs/config

@nestjs/config 的内部其实使用的是 dotenv

如何使用配置

1
2
3
4
5
6
7
8
9
// 从app module中引入
ConfigModule.forRoot({
isGlobal: true,
envFilePath:
process.env.NODE_ENV === 'production'
? []
: [`.env.${process.env.NODE_ENV || 'development'}`],
load: [configFile],
})

默认代码是从项目的根目录加载 .env 文件,合并 env 文件中的键值对,并分配给 process.env 环境变量,并且可以通过 configService 来读取环境变量

环境变量文件应该是这样的:

1
2
DATABASE_USER=test
DATABASE_PASSWORD=test

我们优先是将所有的配置文件放在根目录,并用不同的环境来区分

例如 .env.development.env.test.env.production 分别代表本地、测试和线上环境

当然我们也可以有一些无关紧要的通用配置,比如 .env.local

1
2
3
4
5
6
7
8
9
10
ConfigModule.forRoot({
envFilePath: '.env.development', // 指定单个文件路径
});

// 指定多个文件路径
ConfigModule.forRoot({
envFilePath: ['.env.local', '.env.development'],
});

// 如果有多个配置文件,那么会优先选用第一个文件中的变量,如果第一个文件中没有此变量,会去第二个文件中寻找变量,以此类推

通常我们是全局使用配置,所以可以加入 isGlobal 用来标记全局使用

如何加载配置项

一般我们的项目会涉及到多个不同类型的配置,比如 redismongomysqles等等

所以我们的最佳实践应该是将不同类型的配置文件分开存放

1
2
3
4
5
6
7
8
9
config
|
——————mongo
|
——————mysql
|
——————es
|
——————redis

这样我们可以快速的查看配置项

例如下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//config/configuration.ts  基础配置项
export default () => ({
port: parseInt(process.env.PORT, 10) || 3000,
database: {
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10) || 5432
}
});

// 然后在入口app module中这样引入
import configuration from './config/configuration';
@Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
],
})
export class AppModule {}

我们注意到 load 方法是一个数组,这也就意味着我们可以引入多个配置文件

在nestjs接入数据库、mongo一章我们讲到了如何在nestjs中加载mysql配置,但是我们是直接在代码中写死的,现在我们来改写一下,通过加载本地的 .env.development 文件配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { ConfigModule, ConfigService } from '@nestjs/config';
// 首先是app module中的配置
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const mysql = configService.getOrThrow('mysql', { infer: true });
return {
...mysql,
type: 'mysql',
autoLoadEntities: true,
synchronize: false,
};
},
})

// 我们通过注入configservice,然后通过configService.getOrThrow来获取mysql的配置

其次我们需要知道mysql的配置从哪里来

1
2
3
4
5
6
7
8
9
config
|
——————mongo
|
——————mysql (index.ts)
|
——————es
|
——————redis

我们现在来编写mysql下的 index.ts 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { registerAs } from '@nestjs/config';

export type MysqlConfig = {
  host?: string;
  port: number;
  username?: string;
  password?: string;
  database?: string;
};

export default registerAs<MysqlConfig>('mysql', () => {
  return {
    host: process.env.MYSQL_HOST,
    port: Number(process.env.MYSQL_PORT || 3306),
    username: process.env.MYSQL_USER,
    password: process.env.MYSQL_PASSWORD,
    database: process.env.MYSQL_DB,
  };
});

process.env 下面的变量在哪里定义呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// .env.development

# 基础配置
NODE_ENV=development
APP_PORT=3000

# MySQL
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USER=root
MYSQL_PASSWORD=root
MYSQL_DB=db_nest
# MONGO

MONGO_URI=mongodb://root:root@localhost:27017/db_nest

现在我们把所有配置都已经准备齐全了,那我们通过 configService.getOrThrow('mysql', { infer: true }); 拿到的 mysql 就是 MysqlConfig 命名空间下的导出的所有配置字段

同理,我们还会接入 mongoose 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// app module
MongooseModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
const mongo = configService.getOrThrow('mongo', { infer: true });
return {
uri: mongo.uri,
};
},
}),

// mongo index.ts
import { registerAs } from '@nestjs/config';

export type MongoConfig = {
  uri?: string;
};

export default registerAs<MongoConfig>('mongo', () => {
  return {
    uri: process.env.MONGO_URI,
  };
});

这基本上可以满足我们的配置化需求,但是我们通常还会面临一个问题,我们的本地开发是可以把密码什么的告诉开发者,那如果是线上怎么办呢?我们是不是应该把这些密码等一些关键的信息通过其他的方式打进去呢?

我们可以通过 ci/cd 的方式注入

  • github actions -> Repository Settings-> Secrets and variables -> Actions
  • gitlab -> Settings -> CI/CD -> Variables

构建docker镜像

  • 我们在CI/CD中只打包代码,不打包配置
  • 线上部署时,配置交给 K8s,通过 ConfigMap + Secret 管理
  • Deployment 里注入环境变量CI/CD(GitHub Actions / GitLab CI / Jenkins)在执行到 部署阶段时,把配置注入到 K8s
1
2
3
4
5
6
7
8
- name: Deploy to K8s
run: |
kubectl create secret generic app-secrets \
--from-literal=DB_HOST=${{ secrets.DB_HOST }} \
--from-literal=DB_USER=${{ secrets.DB_USER }} \
--from-literal=DB_PASS=${{ secrets.DB_PASS }} \
--dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f k8s/deployment.yaml

通过consul等类配置中心注入

  • 在 NestJS 启动时,通过 Consul HTTP API 拉取配置
  • 在 CI/CD 流水线里,把敏感配置写入 Consul
  • 部署时应用直接去 Consul 取,不需要 CI/CD 再往 Pod 里打环境变量

nestjs接入动态配置
https://shiyuq.github.io/2025/09/17/nestjs接入动态配置/
作者
Jack
发布于
2025年9月17日
许可协议