在实际项目中,我们需要管理数据库连接、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/wwzhidao
DB_TYPE=mongodb
# JWT 配置
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=24h
# 服务器配置
PORT=3000
NODE_ENV=development
# AI 配置
DEEPSEEK_API_KEY=your-api-key
DEEPSEEK_MODEL=deepseek-chat
MAX_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 1
line 2
line 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=localhost
DATABASE_PORT=27017
DATABASE_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,
}),六、配置命名空间
创建配置命名空间
为不同的配置创建命名空间,提高代码组织性:
// src/config/database.config.ts
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,
}));// src/config/jwt.config.ts
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');类型安全的命名空间
定义配置接口:
// src/config/database.config.ts
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-dev
DB_TYPE=mongodb
# JWT 配置
JWT_SECRET=dev-secret-key-change-in-production
JWT_EXPIRES_IN=24h
# 服务器配置
PORT=3000
NODE_ENV=development
# AI 配置
DEEPSEEK_API_KEY=your-dev-api-key
DEEPSEEK_MODEL=deepseek-chat
MAX_TOKENS=4000
# 日志级别
LOG_LEVEL=debug创建 .env.production 文件:
# 数据库配置
MONGODB_URI=mongodb://prod-host:27017/wwzhidao
DB_TYPE=mongodb
# JWT 配置
JWT_SECRET=prod-secret-key-very-secure
JWT_EXPIRES_IN=7d
# 服务器配置
PORT=8888
NODE_ENV=production
# AI 配置
DEEPSEEK_API_KEY=your-prod-api-key
DEEPSEEK_MODEL=deepseek-chat
MAX_TOKENS=4000
# 日志级别
LOG_LEVEL=info创建 .env.example 文件(提交到 Git):
# 数据库配置
MONGODB_URI=mongodb://localhost:27017/wwzhidao
DB_TYPE=mongodb
# JWT 配置
JWT_SECRET=your-secret-key
JWT_EXPIRES_IN=24h
# 服务器配置
PORT=3000
NODE_ENV=development
# AI 配置
DEEPSEEK_API_KEY=your-api-key
DEEPSEEK_MODEL=deepseek-chat
MAX_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 required
Error: 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-key
DATABASE_PASSWORD=your-password
// ❌ 避免:敏感信息硬编码
const jwtSecret = 'my-secret-key';5. 使用 .env.example 📄
# .env.example(提交到 Git)
MONGODB_URI=mongodb://localhost:27017/mydb
JWT_SECRET=your-secret-key
PORT=3000
# .env(不提交到 Git)
MONGODB_URI=mongodb://localhost:27017/mydb
JWT_SECRET=actual-secret-key
PORT=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 dist
pnpm 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 文档 - 环境变量加载库
- 本文链接:https://fridolph.top/posts/2025-12-01__nest14-config
- 版权声明:本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 许可协议。