【NestJS】14 配置管理(Configuration)
在实际项目中,我们需要管理数据库连接、API 密钥、服务端口等各种配置。不同的环境(开发、测试、生产)需要不同的配置,而敏感信息更不能硬编码在代码中。配置管理可以帮助我们优雅地解决这些问题 ⚙️
一、为什么需要配置管理?
配置管理的必要性 🎯
在实际项目中,我们需要管理各种各样的配置:
- 环境变量:数据库连接、API 密钥、服务端口
- 不同环境:开发环境、测试环境、生产环境的配置差异
- 敏感信息:密码、密钥等不应该硬编码在代码中
- 配置验证:确保配置的正确性和完整性
📚 参考资源:
- 12-Factor App - Config - 配置管理最佳实践
- NestJS Configuration 官方文档 - 官方完整指南
不使用配置管理的问题 ⚠️
如果不使用配置管理,我们会遇到以下问题:
1. 硬编码问题
// ❌ 不推荐:配置直接写在代码中const dbConnection = 'mongodb://localhost:27017/mydb';const jwtSecret = 'my-secret-key';2. 难以维护
- 修改配置需要修改代码并重新部署
- 配置散落在各个文件中,难以统一管理
3. 环境切换困难
- 开发、测试、生产环境需要不同的配置
- 手动修改配置容易出错
4. 缺乏验证
- 无法验证配置的正确性
- 启动时才发现配置错误
5. 安全风险
- 敏感信息可能被提交到代码仓库
- 配置泄露导致安全问题
二、@nestjs/config 模块
安装依赖
首先,安装 @nestjs/config 模块:
pnpm add @nestjs/config@4.0.2📌 注意:本课程使用的版本是 4.0.2
基本配置
在 app.module.ts 中导入 ConfigModule:
import { Module } from '@nestjs/common';import { ConfigModule } from '@nestjs/config';
@Module({ imports: [ ConfigModule.forRoot({ isGlobal: true, // 全局模块,可以在任何地方使用 }), ],})export class AppModule {}配置选项说明:
| 选项 | 类型 | 说明 |
|---|---|---|
isGlobal | boolean | 是否为全局模块,设为 true 后无需在其他模块中重复导入 |
envFilePath | string | string[] | 环境变量文件路径 |
ignoreEnvFile | boolean | 是否忽略 .env 文件 |
validationSchema | Joi.Schema | 配置验证 Schema |
load | Array | 自定义配置加载器 |
📚 参考资源:
- ConfigModule API 文档 - 完整的配置选项
环境变量文件
创建 .env 文件(不要提交到 Git):
# 数据库配置MONGODB_URI=mongodb://localhost:27017/wwzhidaoDB_TYPE=mongodb
# JWT 配置JWT_SECRET=your-secret-keyJWT_EXPIRES_IN=24h
# 服务器配置PORT=3000NODE_ENV=development
# AI 配置DEEPSEEK_API_KEY=your-api-keyDEEPSEEK_MODEL=deepseek-chatMAX_TOKENS=4000环境变量命名规范:
- 使用大写字母和下划线
- 使用有意义的名称
- 相关配置使用相同前缀(如
DB_、JWT_)
使用配置
在服务中使用 ConfigService:
import { Injectable } from '@nestjs/common';import { ConfigService } from '@nestjs/config';
@Injectable()export class UserService { constructor(private configService: ConfigService) {}
someMethod() { // 获取配置 const dbUri = this.configService.get<string>('MONGODB_URI');
// 提供默认值 const port = this.configService.get<number>('PORT', 3000);
// 如果配置不存在会抛出异常 const jwtSecret = this.configService.getOrThrow<string>('JWT_SECRET'); }}三、环境变量文件
.env 文件格式
.env 文件用于存储环境变量,通常放在项目根目录。
格式规范:
# 基本格式KEY=value
# 带空格的值需要用引号KEY_WITH_SPACES="value with spaces"
# 注释# 这是一行注释KEY_WITH_COMMENT=value # 行尾注释
# 多行值(使用引号)MULTI_LINE="line 1line 2line 3"多环境配置
可以为不同环境创建不同的配置文件:
project/├── .env # 默认配置├── .env.development # 开发环境├── .env.production # 生产环境├── .env.test # 测试环境└── .env.example # 配置模板(提交到 Git)指定环境文件:
ConfigModule.forRoot({ envFilePath: '.env.development', // 指定环境文件 isGlobal: true,}),根据环境变量选择:
ConfigModule.forRoot({ envFilePath: `.env.${process.env.NODE_ENV || 'development'}`, isGlobal: true,}),多个环境文件
可以指定多个环境文件,后面的会覆盖前面的:
ConfigModule.forRoot({ envFilePath: ['.env.local', '.env'], // .env.local 优先级更高 isGlobal: true,}),优先级顺序:
- 系统环境变量(最高优先级)
.env.local文件.env文件- 代码中的默认值(最低优先级)
四、ConfigService 的使用
基本用法
使用 ConfigService 获取配置:
import { ConfigService } from '@nestjs/config';
constructor(private configService: ConfigService) {}
someMethod() { // 基本获取 const value = this.configService.get<string>('KEY');
// 获取嵌套配置 const host = this.configService.get<string>('database.host');
// 获取整个配置对象 const dbConfig = this.configService.get('database');}类型安全
使用泛型指定配置类型:
// 字符串类型const dbUri = this.configService.get<string>('MONGODB_URI');
// 数字类型const port = this.configService.get<number>('PORT');
// 布尔类型const isDev = this.configService.get<boolean>('IS_DEV');
// 自定义类型interface DatabaseConfig { host: string; port: number; database: string;}
const dbConfig = this.configService.get<DatabaseConfig>('database');默认值
提供默认值,避免配置缺失:
// 提供默认值const port = this.configService.get<number>('PORT', 3000);const timeout = this.configService.get<number>('TIMEOUT', 5000);
// 默认值可以是任何类型const features = this.configService.get<string[]>('FEATURES', ['feature1', 'feature2']);获取嵌套配置
使用点号访问嵌套配置:
// .env 文件DATABASE_HOST=localhostDATABASE_PORT=27017DATABASE_NAME=mydb
// 代码中const host = this.configService.get<string>('DATABASE_HOST');const port = this.configService.get<number>('DATABASE_PORT');const name = this.configService.get<string>('DATABASE_NAME');检查配置是否存在
方法 1:手动检查
const apiKey = this.configService.get<string>('API_KEY');if (!apiKey) { throw new Error('API_KEY 未配置');}方法 2:使用 getOrThrow()
// 如果配置不存在,会自动抛出异常const apiKey = this.configService.getOrThrow<string>('API_KEY');五、配置验证
使用 Joi 验证配置
安装 Joi:
pnpm add joi@18.0.2创建配置验证 Schema:
import * as Joi from 'joi';
export const configValidationSchema = Joi.object({ // 环境 NODE_ENV: Joi.string() .valid('development', 'production', 'test') .default('development'),
// 端口 PORT: Joi.number().default(3000),
// 数据库 MONGODB_URI: Joi.string().required(),
// JWT JWT_SECRET: Joi.string().required().min(12), JWT_EXPIRES_IN: Joi.string().default('24h'),
// AI 配置 DEEPSEEK_API_KEY: Joi.string().required(), DEEPSEEK_MODEL: Joi.string().default('deepseek-chat'), MAX_TOKENS: Joi.number().default(4000),});📚 参考资源:
- Joi 官方文档 - 完整的验证规则
在 ConfigModule 中使用:
import { configValidationSchema } from './config/config.schema';
ConfigModule.forRoot({ validationSchema: configValidationSchema, validationOptions: { allowUnknown: true, // 允许未知的配置项 abortEarly: true, // 遇到第一个错误就停止 },}),验证选项说明:
| 选项 | 类型 | 说明 |
|---|---|---|
allowUnknown | boolean | 是否允许未定义的配置项 |
abortEarly | boolean | 是否在第一个错误时停止验证 |
stripUnknown | boolean | 是否移除未定义的配置项 |
使用 class-validator 验证
也可以使用 class-validator 验证配置:
import { IsString, IsNumber, IsOptional, IsEnum } from 'class-validator';import { plainToClass } from 'class-transformer';
enum Environment { Development = 'development', Production = 'production', Test = 'test',}
export class EnvironmentVariables { @IsEnum(Environment) NODE_ENV: Environment;
@IsNumber() @IsOptional() PORT?: number;
@IsString() MONGODB_URI: string;
@IsString() JWT_SECRET: string;
@IsString() @IsOptional() DEEPSEEK_API_KEY?: string;}
export function validate(config: Record<string, unknown>) { const validatedConfig = plainToClass(EnvironmentVariables, config, { enableImplicitConversion: true, });
const errors = validateSync(validatedConfig, { skipMissingProperties: false, });
if (errors.length > 0) { throw new Error(errors.toString()); }
return validatedConfig;}在 ConfigModule 中使用:
ConfigModule.forRoot({ validate,}),六、配置命名空间
创建配置命名空间
为不同的配置创建命名空间,提高代码组织性:
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({ uri: process.env.MONGODB_URI, host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT, 10) || 27017, name: process.env.DB_NAME,}));import { registerAs } from '@nestjs/config';
export default registerAs('jwt', () => ({ secret: process.env.JWT_SECRET, expiresIn: process.env.JWT_EXPIRES_IN || '24h',}));使用配置命名空间
在 ConfigModule 中加载:
import databaseConfig from './config/database.config';import jwtConfig from './config/jwt.config';
ConfigModule.forRoot({ load: [databaseConfig, jwtConfig], // 加载配置命名空间}),在服务中使用:
// 获取整个命名空间const dbConfig = this.configService.get('database');console.log(dbConfig); // { uri: '...', host: '...', port: 27017 }
// 获取命名空间中的特定配置const uri = this.configService.get('database.uri');const port = this.configService.get<number>('database.port');类型安全的命名空间
定义配置接口:
export interface DatabaseConfig { uri: string; host: string; port: number; name: string;}
export default registerAs('database', (): DatabaseConfig => ({ uri: process.env.MONGODB_URI, host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT, 10) || 27017, name: process.env.DB_NAME,}));使用时:
import { DatabaseConfig } from './config/database.config';
const dbConfig = this.configService.get<DatabaseConfig>('database');七、实战:多环境配置系统
需求分析 📋
创建一个完整的多环境配置系统,实现以下功能:
- 开发环境配置
- 生产环境配置
- 配置验证
- 在不同环境中使用不同的配置
完整实现
第一步:创建环境文件
创建 .env.development 文件:
# 数据库配置MONGODB_URI=mongodb://localhost:27017/wwzhidao-devDB_TYPE=mongodb
# JWT 配置JWT_SECRET=dev-secret-key-change-in-productionJWT_EXPIRES_IN=24h
# 服务器配置PORT=3000NODE_ENV=development
# AI 配置DEEPSEEK_API_KEY=your-dev-api-keyDEEPSEEK_MODEL=deepseek-chatMAX_TOKENS=4000
# 日志级别LOG_LEVEL=debug创建 .env.production 文件:
# 数据库配置MONGODB_URI=mongodb://prod-host:27017/wwzhidaoDB_TYPE=mongodb
# JWT 配置JWT_SECRET=prod-secret-key-very-secureJWT_EXPIRES_IN=7d
# 服务器配置PORT=8888NODE_ENV=production
# AI 配置DEEPSEEK_API_KEY=your-prod-api-keyDEEPSEEK_MODEL=deepseek-chatMAX_TOKENS=4000
# 日志级别LOG_LEVEL=info创建 .env.example 文件(提交到 Git):
# 数据库配置MONGODB_URI=mongodb://localhost:27017/wwzhidaoDB_TYPE=mongodb
# JWT 配置JWT_SECRET=your-secret-keyJWT_EXPIRES_IN=24h
# 服务器配置PORT=3000NODE_ENV=development
# AI 配置DEEPSEEK_API_KEY=your-api-keyDEEPSEEK_MODEL=deepseek-chatMAX_TOKENS=4000第二步:创建配置验证 Schema
创建 src/config/config.schema.ts 文件:
import * as Joi from 'joi';
export const configValidationSchema = Joi.object({ // 环境 NODE_ENV: Joi.string() .valid('development', 'production', 'test') .default('development'),
// 服务器 PORT: Joi.number().default(3000),
// 数据库 MONGODB_URI: Joi.string().required(), DB_TYPE: Joi.string().valid('mongodb', 'postgres').default('mongodb'),
// JWT JWT_SECRET: Joi.string().required().min(12), JWT_EXPIRES_IN: Joi.string().default('24h'),
// AI 配置 DEEPSEEK_API_KEY: Joi.string().required(), DEEPSEEK_MODEL: Joi.string().default('deepseek-chat'), MAX_TOKENS: Joi.number().default(4000),
// 日志 LOG_LEVEL: Joi.string() .valid('error', 'warn', 'info', 'debug') .default('info'),});第三步:配置 ConfigModule
在 app.module.ts 中配置:
import { Module } from '@nestjs/common';import { ConfigModule } from '@nestjs/config';import { configValidationSchema } from './config/config.schema';
@Module({ imports: [ ConfigModule.forRoot({ // 根据环境变量选择配置文件 envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
// 全局模块 isGlobal: true,
// 配置验证 validationSchema: configValidationSchema, validationOptions: { allowUnknown: true, abortEarly: true, },
// 缓存配置(生产环境推荐) cache: process.env.NODE_ENV === 'production', }), ],})export class AppModule {}第四步:创建配置服务
创建 src/config/config.service.ts 文件:
import { Injectable } from '@nestjs/common';import { ConfigService } from '@nestjs/config';
@Injectable()export class AppConfigService { constructor(private configService: ConfigService) {}
// 数据库配置 get databaseUri(): string { return this.configService.getOrThrow<string>('MONGODB_URI'); }
get databaseType(): string { return this.configService.get<string>('DB_TYPE', 'mongodb'); }
// JWT 配置 get jwtSecret(): string { return this.configService.getOrThrow<string>('JWT_SECRET'); }
get jwtExpiresIn(): string { return this.configService.get<string>('JWT_EXPIRES_IN', '24h'); }
// 服务器配置 get port(): number { return this.configService.get<number>('PORT', 3000); }
get nodeEnv(): string { return this.configService.get<string>('NODE_ENV', 'development'); }
get isProduction(): boolean { return this.nodeEnv === 'production'; }
get isDevelopment(): boolean { return this.nodeEnv === 'development'; }
// AI 配置 get deepseekApiKey(): string { return this.configService.getOrThrow<string>('DEEPSEEK_API_KEY'); }
get deepseekModel(): string { return this.configService.get<string>('DEEPSEEK_MODEL', 'deepseek-chat'); }
get maxTokens(): number { return this.configService.get<number>('MAX_TOKENS', 4000); }}第五步:修改 database.module.ts
import { Module } from '@nestjs/common';import { ConfigModule, ConfigService } from '@nestjs/config';import { DatabaseService } from './database.service';
@Module({ imports: [ConfigModule], providers: [ DatabaseService, { provide: 'DATABASE_CONNECTION', useFactory: (configService: ConfigService) => { const dbType = configService.get('DB_TYPE', 'mongodb');
if (dbType === 'mongodb') { return { type: 'mongodb', uri: configService.getOrThrow('MONGODB_URI'), }; } else if (dbType === 'postgres') { return { type: 'postgres', host: configService.get('POSTGRES_HOST'), port: configService.get('POSTGRES_PORT'), database: configService.get('POSTGRES_DB'), }; }
throw new Error(`不支持的数据库类型: ${dbType}`); }, inject: [ConfigService], }, ], exports: ['DATABASE_CONNECTION', DatabaseService],})export class DatabaseModule {}第六步:在 main.ts 中使用配置
import { NestFactory } from '@nestjs/core';import { AppModule } from './app.module';import { ConfigService } from '@nestjs/config';
async function bootstrap() { const app = await NestFactory.create(AppModule);
// 获取配置服务 const configService = app.get(ConfigService);
// 使用配置 const port = configService.get<number>('PORT', 3000); const nodeEnv = configService.get<string>('NODE_ENV', 'development');
await app.listen(port);
console.log(`应用运行在端口 ${port},环境:${nodeEnv}`);}
bootstrap();测试验证 ✅
1. 启动开发环境
NODE_ENV=development pnpm start:dev2. 启动生产环境
NODE_ENV=production pnpm start:prod3. 配置验证失败示例
如果配置验证失败,会显示错误信息:
Error: Config validation error: "JWT_SECRET" is requiredError: Config validation error: "JWT_SECRET" length must be at least 12 characters long八、配置管理最佳实践
1. 使用环境变量 🌐
// ✅ 推荐:使用环境变量const dbUri = this.configService.get('MONGODB_URI');
// ❌ 避免:硬编码const dbUri = 'mongodb://localhost:27017/mydb';2. 提供默认值 📝
// ✅ 推荐:提供合理的默认值const port = this.configService.get<number>('PORT', 3000);const timeout = this.configService.get<number>('TIMEOUT', 5000);
// ⚠️ 注意:敏感配置不应该有默认值const jwtSecret = this.configService.getOrThrow<string>('JWT_SECRET');3. 验证配置 ✅
// ✅ 推荐:使用 Joi 验证配置export const configValidationSchema = Joi.object({ JWT_SECRET: Joi.string().required().min(12), PORT: Joi.number().default(3000),});4. 分离敏感信息 🔒
// ✅ 推荐:敏感信息通过环境变量提供JWT_SECRET=your-secret-keyDATABASE_PASSWORD=your-password
// ❌ 避免:敏感信息硬编码const jwtSecret = 'my-secret-key';5. 使用 .env.example 📄
# .env.example(提交到 Git)MONGODB_URI=mongodb://localhost:27017/mydbJWT_SECRET=your-secret-keyPORT=3000
# .env(不提交到 Git)MONGODB_URI=mongodb://localhost:27017/mydbJWT_SECRET=actual-secret-keyPORT=30006. 不要提交 .env 文件 🚫
在 .gitignore 中添加:
# 环境变量文件.env.env.local.env.development.local.env.production.local
# 但保留示例文件!.env.example7. 配置命名空间 📦
// ✅ 推荐:使用命名空间组织配置export default registerAs('database', () => ({ uri: process.env.MONGODB_URI, port: parseInt(process.env.DB_PORT, 10),}));8. 类型安全 🛡️
// ✅ 推荐:使用泛型和接口interface DatabaseConfig { uri: string; port: number;}
const dbConfig = this.configService.get<DatabaseConfig>('database');九、常见问题排查
问题 1:配置未加载
问题描述:ConfigService.get() 返回 undefined
可能原因:
- 环境变量未设置
- 环境文件路径错误
- ConfigModule 未正确配置
解决方案:
// ✅ 检查环境变量是否设置console.log(process.env.MONGODB_URI);
// ✅ 检查环境文件路径ConfigModule.forRoot({ envFilePath: `.env.${process.env.NODE_ENV}`, isGlobal: true,}),
// ✅ 使用 getOrThrow 确保配置存在const uri = this.configService.getOrThrow<string>('MONGODB_URI');问题 2:配置验证失败
问题描述:启动时配置验证失败
可能原因:环境变量不符合验证 Schema
解决方案:
// ✅ 检查验证规则export const configValidationSchema = Joi.object({ JWT_SECRET: Joi.string().required().min(12), // 至少 12 个字符 PORT: Joi.number().default(3000),});
// ✅ 检查环境变量JWT_SECRET=your-very-secure-secret-key # 至少 12 个字符问题 3:类型错误
问题描述:配置类型不正确
解决方案:
// ✅ 使用泛型指定类型const port = this.configService.get<number>('PORT');
// ✅ 使用类型转换const port = parseInt(this.configService.get('PORT'), 10);
// ✅ 在配置命名空间中转换类型export default registerAs('database', () => ({ port: parseInt(process.env.DB_PORT, 10) || 27017,}));问题 4:环境切换不生效
问题描述:修改 NODE_ENV 后配置未更新
解决方案:
# ✅ 确保环境变量正确设置NODE_ENV=production pnpm start
# ✅ 检查配置文件是否存在ls .env.production
# ✅ 清除缓存后重启rm -rf distpnpm start:prod总结 🎯
这节课我们系统学习了 NestJS 配置管理的核心概念和实战应用。
核心知识点 ✅
- ✅ 理解了配置管理的必要性:管理环境变量、不同环境配置、敏感信息
- ✅ 学习了 @nestjs/config 模块:使用 ConfigModule 管理配置
- ✅ 掌握了环境变量文件:创建和使用 .env 文件,支持多环境配置
- ✅ 学习了 ConfigService 的使用:获取配置、提供默认值、类型安全
- ✅ 学习了配置验证:使用 Joi 或 class-validator 验证配置
- ✅ 学习了配置命名空间:为不同的配置创建命名空间
- ✅ 完成了实战练习:实现了多环境配置系统
- ✅ 掌握了配置的最佳实践:包括使用环境变量、提供默认值、验证配置等
配置管理核心概念对比 📊
| 概念 | 说明 | 示例 |
|---|---|---|
| 环境变量 | 存储在系统中的配置 | process.env.PORT |
| .env 文件 | 存储环境变量的文件 | .env.development |
| ConfigService | 获取配置的服务 | configService.get('PORT') |
| 配置验证 | 验证配置的正确性 | Joi Schema |
| 配置命名空间 | 组织配置的方式 | registerAs('database', ...) |
下节课预告 🚀
下一节课,我们将学习 异步编程与 RxJS,这是 NestJS 中处理异步操作的重要机制。RxJS 提供了强大的响应式编程能力,可以:
- 🔄 处理复杂的异步流
- 🎯 组合多个异步操作
- ⚡ 优化性能和资源使用
- 🛡️ 更好地处理错误
延伸学习资源 📚
- NestJS 官方文档 - Configuration - 官方完整指南
- 12-Factor App - Config - 配置管理最佳实践
- Joi 官方文档 - 配置验证库
- dotenv 文档 - 环境变量加载库
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!