{T}

NestJS多环境配置进阶学习笔记

NestJS多环境配置进阶学习笔记

核心知识点

1. TypeScript 源码导航技巧

1.1 快速查看定义

在 VSCode 中,按住 Command(Mac)或 Control(Windows)键,点击代码中的模块、方法或属性,即可跳转到其类型定义文件

1.2 通过注释了解 API

跳转到定义后,通过**官方文档注释(JSDoc)**即可快速了解该属性/方法的作用和支持的参数:

typescript
// 点击 ConfigModule.forRoot 跳转到定义后可见:
interface ConfigModuleOptions {
  isGlobal?: boolean;       // "If true, registers ConfigModule as a global module"
  ignoreEnvFile?: boolean;  // "If true, it will ignore the .env file"
  envFilePath?: string;     // "Custom path to the .env file"
  load?: ConfigFactory[];   // Custom config loading functions
  // ...
}

技巧:善用 TypeScript 的类型跳转,可以在不查文档的情况下快速了解 API 用法。


2. envFilePath 指定环境文件

2.1 ConfigModule.forRoot 可选属性

通过查看源码定义,ConfigModule.forRoot() 支持以下关键属性:

属性类型作用
isGlobalboolean是否为全局模块
ignoreEnvFileboolean是否忽略 .env 文件
envFilePathstring指定 .env 文件路径
loadConfigFactory[]自定义配置加载函数数组

2.2 创建多环境文件

code
project-root/
├── .env                    # 公共共享配置
├── .env.development        # 开发环境专属配置
├── .env.production         # 生产环境专属配置
└── src/
    └── app.module.ts

.env.development

env
DB=mysql_dev

.env.production

env
DB=mysql_pro

2.3 动态指定 envFilePath

使用 process.env.NODE_ENV 动态决定加载哪个环境文件:

typescript
// app.module.ts
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      // 动态拼接文件路径:.env.development 或 .env.production
      envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
    }),
  ],
})
export class AppModule {}

2.4 使用 cross-env 设置环境变量

安装跨平台环境变量工具:

bash
pnpm install cross-env -D

package.json 中配置脚本:

json
{
  "scripts": {
    "start:dev": "cross-env NODE_ENV=development nest start --watch",
    "start:pro": "cross-env NODE_ENV=production node dist/main"
  }
}

cross-env:解决 Windows 和 Mac/Linux 设置环境变量命令不兼容的问题。Windows 用 set NODE_ENV=xxx,Mac/Linux 用 export NODE_ENV=xxxcross-env 统一了写法。

2.5 运行效果

bash
# 开发环境
pnpm start:dev
# → 加载 .env.development → DB = 'mysql_dev'

# 生产环境
pnpm start:pro
# → 加载 .env.production → DB = 'mysql_pro'

3. envFilePath 的局限性

3.1 存在的问题

当项目配置项较多时,.env.development.env.production 中会存在大量重复配置

env
# .env.development
DB=mysql_dev
DB_HOST=127.0.0.1      # ← 与 production 重复
DB_PORT=3306            # ← 与 production 重复
DB_USERNAME=root        # ← 与 production 重复
DB_URL=https://xxx.com  # ← 与 production 重复

# .env.production
DB=mysql_pro
DB_HOST=127.0.0.1      # ← 重复!
DB_PORT=3306            # ← 重复!
DB_USERNAME=root        # ← 重复!
DB_URL=https://xxx.com  # ← 重复!

痛点

  • 新增公共配置时,需要同时在两个文件中添加,容易遗漏
  • 修改公共配置时,需要同时修改两个文件,维护成本高
  • .env 文件与 .env.development / .env.production 之间没有关联关系

3.2 解决思路

需要一个共享配置文件.env),存放两个环境都需要的公共配置,各环境文件只存放差异配置。


4. load 属性 + dotenv 实现共享配置

4.1 核心原理

ConfigModule.forRoot()load 属性接收一个函数数组,每个函数返回一个键值对对象。该对象中的值会合并到配置中

typescript
load?: ConfigFactory[];
// ConfigFactory = () => Record<string, any>

4.2 安装 dotenv

bash
pnpm install dotenv

4.3 配置共享文件

.env(公共配置)

env
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_URL=https://3w.mock.com

.env.development(开发环境差异)

env
DB=mysql_dev
DB_URL=https://3w.mock1.com

.env.production(生产环境差异)

env
DB=mysql_pro

4.4 在 AppModule 中配置 load

typescript
// app.module.ts
import { ConfigModule } from '@nestjs/config';
import * as dotenv from 'dotenv';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
      // 加载 .env 公共共享配置
      load: [
        () => dotenv.config({ path: '.env' }),
      ],
    }),
  ],
})
export class AppModule {}

4.5 配置合并与覆盖规则

code
优先级(从高到低):
  .env.development / .env.production  →  覆盖
  .env(通过 load 加载)              →  被覆盖
配置项.env(共享).env.development最终值
DB-mysql_devmysql_dev
DB_HOST127.0.0.1-127.0.0.1
DB_PORT3306-3306
DB_URLhttps://3w.mock.comhttps://3w.mock1.comhttps://3w.mock1.com 被覆盖

关键规则:环境专属文件的配置会覆盖共享文件中的同名配置项。


5. 完整配置文件结构

code
project-root/
├── .env                          # 公共共享配置(host、port、username 等)
├── .env.development              # 开发环境差异配置
├── .env.production               # 生产环境差异配置
├── .gitignore                    # 忽略 .env* 文件
├── package.json
└── src/
    ├── app.module.ts             # ConfigModule 注册
    ├── enum/
    │   └── config.enum.ts        # 配置键名枚举
    └── user/
        ├── user.module.ts
        └── user.controller.ts    # ConfigService 使用

代码实战案例

需求描述

构建一个支持多环境的 NestJS 配置系统,实现 .env 共享配置 + .env.development / .env.production 差异配置的自动合并。

完整实现

第一步:安装依赖

bash
pnpm install @nestjs/config dotenv cross-env -D

第二步:创建配置文件

.env(公共配置):

env
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USERNAME=root
DB_URL=https://3w.mock.com

.env.development(开发环境差异):

env
DB=mysql_dev
DB_URL=https://dev.mock.com

.env.production(生产环境差异):

env
DB=mysql_pro
DB_URL=https://prod.mock.com

第三步:配置 AppModule

typescript
// src/app.module.ts
import { ConfigModule } from '@nestjs/config';
import { Module } from '@nestjs/common';
import * as dotenv from 'dotenv';
import { UserModule } from './user/user.module';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      // 动态加载对应环境的 .env 文件
      envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
      // 加载公共共享配置
      load: [() => dotenv.config({ path: '.env' })],
    }),
    UserModule,
  ],
})
export class AppModule {}

第四步:定义枚举并使用

typescript
// src/enum/config.enum.ts
export enum ConfigEnum {
  DB = 'DB',
  DB_HOST = 'DB_HOST',
  DB_PORT = 'DB_PORT',
  DB_USERNAME = 'DB_USERNAME',
  DB_URL = 'DB_URL',
}
typescript
// src/user/user.controller.ts
import { Controller, Get } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ConfigEnum } from '../enum/config.enum';

@Controller('user')
export class UserController {
  constructor(private configService: ConfigService) {}

  @Get()
  getUsers() {
    console.log('DB 类型:', this.configService.get(ConfigEnum.DB));
    console.log('DB 地址:', this.configService.get(ConfigEnum.DB_HOST));
    console.log('DB 端口:', this.configService.get(ConfigEnum.DB_PORT));
    console.log('测试 URL:', this.configService.get(ConfigEnum.DB_URL));
    return { users: [] };
  }
}

第五步:配置脚本并测试

json
// package.json
{
  "scripts": {
    "start:dev": "cross-env NODE_ENV=development nest start --watch",
    "start:pro": "cross-env NODE_ENV=production node dist/main"
  }
}

测试结果

bash
# 开发环境
pnpm start:dev
# → DB: mysql_dev
# → DB_HOST: 127.0.0.1(来自共享 .env)
# → DB_PORT: 3306(来自共享 .env)
# → DB_URL: https://dev.mock.com(development 覆盖了 .env)

# 生产环境
pnpm start:pro
# → DB: mysql_pro
# → DB_HOST: 127.0.0.1(来自共享 .env)
# → DB_PORT: 3306(来自共享 .env)
# → DB_URL: https://prod.mock.com(production 覆盖了 .env)

常见问题与解决方案

问题原因解决方案
envFilePath 设置后 .env 不生效envFilePath 指向了其他文件,默认 .env 被忽略使用 load 属性手动加载 .env 共享文件
环境专属配置未覆盖共享配置配置项键名不一致确保两个文件中键名完全相同
Windows 环境变量设置失败export 命令不兼容 Windows使用 cross-env 统一设置
修改 .env 后配置未更新需要重启应用Ctrl+C 后重新启动
不确定 forRoot 支持哪些属性-Cmd/Ctrl + Click 跳转到类型定义查看

学习要点总结

  1. TypeScript 类型跳转Cmd/Ctrl + Click 可快速查看源码定义和文档注释,是学习第三方库的高效方式
  2. envFilePath 支持动态指定 .env 文件路径,结合 process.env.NODE_ENV 实现多环境切换
  3. cross-env 解决跨平台环境变量设置兼容性问题
  4. load 属性:接受函数数组,每个函数返回键值对对象,用于加载共享配置
  5. 配置覆盖规则:环境专属文件(.env.development)> 共享文件(.env),同名键值后者覆盖前者

延伸学习资源

官方文档

后续课程预告

  • 配置校验:使用 Joi.env 配置进行类型校验和必填校验
  • 命名空间配置:按模块拆分配置文件的 forFeature() 用法

配置管理最佳实践

code
.env                  → 公共配置(host、port 等通用值)
.env.development      → 开发环境差异(dev 数据库、调试开关等)
.env.production       → 生产环境差异(prod 数据库、性能配置等)
.env.example          → 配置模板(提交到 Git,不含实际值)
.gitignore            → 忽略 .env、.env.development、.env.production