Nest(NestJS)基础复习05 - 配置管理 详细拆解
- 2026-10-16 03:59:30
@nestjs/config 的核心价值在于将配置与代码解耦,让同一份代码在不同环境运行不同配置。
// app.module.tsimport { Module } from '@nestjs/common';import { ConfigModule } from '@nestjs/config';@Module({imports: [ConfigModule.forRoot(), // 默认读取项目根目录的 .env 文件],})export class AppModule {}
# .env(项目根目录,通常提交到仓库保存公共默认值)POSTGRES_HOST=localhostPOSTGRES_PORT=5432
// 任何需要配置的地方注入 ConfigService@Injectable()export class AppService {constructor(privateconfigService: ConfigService) {}getDbHost() {return this.configService.get<string>('POSTGRES_HOST'); // 'localhost'}}
.env 是 "默认值仓库",代码里永远不写死 host/port,
只通过 ConfigService 读取 —— 换环境就换 .env,代码一行不用改。
2. 加载优先级:系统环境变量 > .env
默认不会覆盖已存在的系统环境变量(即系统环境变量优先级更高)。
# .env 文件里写的是POSTGRES_HOST=localhost# 但启动前在 shell 里 export 了系统变量export POSTGRES_HOST=prod-db.example.com
const host = this.configService.get<string>('POSTGRES_HOST');// 结果是 'prod-db.example.com' —— 系统变量优先,.env 没覆盖它
这其实是安全特性—— 生产环境常用 "外部注入"(Docker/K8s 的 env、CI 的 secret),
如果 .env 能覆盖系统变量,反而容易被仓库里的旧值污染。默认 "系统变量优先" 保证外部注入永远生效。
3. ignoreEnvFile 与 expandVariables
ignoreEnvFile: true 完全忽略 .env;
expandVariables: true 支持 .env 内引用其他变量。
// 场景 A:完全不用 .env(比如配置全部由 K8s/容器注入)ConfigModule.forRoot({ignoreEnvFile: true,});
// 场景 B:.env 内部互相引用(避免重复写同一段值)ConfigModule.forRoot({expandVariables: true,});# .env(expandVariables: true 时)HOST=db.example.comPORT=5432DB_URL=${HOST}:${PORT} # 自动展开为 db.example.com:5432
expandVariables 可以 "定义一次、处处引用",适合 HOST/PORT 这类会同时出现在多个变量里的值,改一处即可。
4. 多环境配置策略:envFilePath 优先级
envFilePath 数组按优先级加载,排在前面的文件优先级更高;
['.env.local', '.env'] 表示 .env.local 覆盖 .env,而 .env.local 不提交到仓库,用于本地开发覆盖。
ConfigModule.forRoot({envFilePath: ['.env.local', '.env'], // 前面优先!});
# .env(提交到 git:公共默认值)POSTGRES_HOST=localhostPOSTGRES_PORT=5432# .env.local(gitignore 掉:本地私有覆盖,比如连自己电脑上的数据库)POSTGRES_HOST=192.168.1.100
const host = this.configService.get<string>('POSTGRES_HOST');// 结果是 '192.168.1.100' —— .env.local 排在前面,覆盖了 .env
三套环境的标准做法:
# .env.development / .env.test / .env.production 各一份# package.json 里用 cross-env 切 NODE_ENV"start:dev": "cross-env NODE_ENV=development nest start","start:test": "cross-env NODE_ENV=test nest start","start:prod": "cross-env NODE_ENV=production nest start"
// app.module.ts:按 NODE_ENV 选文件ConfigModule.forRoot({envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],// 例如 production 时:先读 .env.production,再兜底 .env});
这是一个 "分层覆盖" 思路 —— 仓库里放公共默认(.env),环境专属(.env.production)按需覆盖,本地私有(.env.local)永远最高。同一个 key 谁在前谁赢。
5. ConfigService 的类型安全
@nestjs/config 的 ConfigService 是泛型类,
如 ConfigService<EnvConfig>,config.get('database.host') 就能获得类型提示和编译时检查,避免拼写错误。
要真正获得类型提示,需要同时满足两点:
① 泛型接口的结构要和配置的层级一致;
② get() 传 { infer: true } 让 TypeScript 从泛型里推导类型。
// env.types.ts —— 定义配置的"形状"interface EnvConfig {database: {host: string;port: number;password: string;};jwt: {secret: string;};}
// app.service.tsimport { Injectable } from '@nestjs/common';import { ConfigService } from '@nestjs/config';import { EnvConfig } from './env.types';@Injectable()export class AppService {// 泛型:告诉 ConfigService "我的配置长这样"constructor(privateconfigService: ConfigService<EnvConfig>) {}getDbHost(): string {// infer: true —— 从 EnvConfig 推导出 string,拼错 key 直接编译报错return this.configService.get('database.host', { infer: true });}getJwtSecret(): string {// get = 可能拿不到(返回undefined),不报错;// getOrThrow = 拿不到就立刻报错,让问题在启动时暴露。// 配置错误是 "越早暴露越好" 的错误。// getOrThrow 把问题从 "运行时某次请求才炸" 提前到 "启动就炸"return this.configService.getOrThrow('jwt.secret', { infer: true });}}
// ✅ 拼对:编译通过,类型是 stringconst host = this.configService.get('database.host', { infer: true });// ❌ 拼错:编译直接报错,而不是运行时才暴露const host = this.configService.get('database.hostt', { infer: true });// Error: Argument of type '"database.hostt"' is not assignable to ...
作用:
类型安全 把 "配置 key 拼写错误" 从运行时崩溃提前到编译时报错 —— 配置这种东西一旦在运行时才发现错,通常已经是线上事故了。
6. 总结:为什么需要配置管理
| 灵活性 | ||
| 安全性 | .env(进 .gitignore)或由系统注入 | |
| 易维护 | .env* 文件和 ConfigModule 一处管理 |
.env 含密码等敏感信息时,务必加入 .gitignore;
真正机密(线上密码)建议连 .env 都不放,直接用系统环境变量 或 密钥管理服务( Vault/KMS ==》 通过加密、动态生成 等手段)注入。
总结:
加载(forRoot + envFilePath/ignoreEnvFile/expandVariables)
读取(get/getOrThrow + 泛型 infer)
分层(系统变量 > .env 文件 > 默认值)