【NestJS】14 配置管理(Configuration)

3492 字
17 分钟
【NestJS】14 配置管理(Configuration)

在实际项目中,我们需要管理数据库连接、API 密钥、服务端口等各种配置。不同的环境(开发、测试、生产)需要不同的配置,而敏感信息更不能硬编码在代码中。配置管理可以帮助我们优雅地解决这些问题 ⚙️

一、为什么需要配置管理?#

配置管理的必要性 🎯#

在实际项目中,我们需要管理各种各样的配置:

  • 环境变量:数据库连接、API 密钥、服务端口
  • 不同环境:开发环境、测试环境、生产环境的配置差异
  • 敏感信息:密码、密钥等不应该硬编码在代码中
  • 配置验证:确保配置的正确性和完整性

📚 参考资源

不使用配置管理的问题 ⚠️#

如果不使用配置管理,我们会遇到以下问题:

1. 硬编码问题

// ❌ 不推荐:配置直接写在代码中
const dbConnection = 'mongodb://localhost:27017/mydb';
const jwtSecret = 'my-secret-key';

2. 难以维护

  • 修改配置需要修改代码并重新部署
  • 配置散落在各个文件中,难以统一管理

3. 环境切换困难

  • 开发、测试、生产环境需要不同的配置
  • 手动修改配置容易出错

4. 缺乏验证

  • 无法验证配置的正确性
  • 启动时才发现配置错误

5. 安全风险

  • 敏感信息可能被提交到代码仓库
  • 配置泄露导致安全问题

二、@nestjs/config 模块#

安装依赖#

首先,安装 @nestjs/config 模块:

Terminal window
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 {}

配置选项说明

选项类型说明
isGlobalboolean是否为全局模块,设为 true 后无需在其他模块中重复导入
envFilePathstring | string[]环境变量文件路径
ignoreEnvFileboolean是否忽略 .env 文件
validationSchemaJoi.Schema配置验证 Schema
loadArray自定义配置加载器

📚 参考资源

环境变量文件#

创建 .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,
}),

优先级顺序

  1. 系统环境变量(最高优先级)
  2. .env.local 文件
  3. .env 文件
  4. 代码中的默认值(最低优先级)

四、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:

Terminal window
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),
});

📚 参考资源

ConfigModule 中使用:

import { configValidationSchema } from './config/config.schema';
ConfigModule.forRoot({
validationSchema: configValidationSchema,
validationOptions: {
allowUnknown: true, // 允许未知的配置项
abortEarly: true, // 遇到第一个错误就停止
},
}),

验证选项说明

选项类型说明
allowUnknownboolean是否允许未定义的配置项
abortEarlyboolean是否在第一个错误时停止验证
stripUnknownboolean是否移除未定义的配置项

使用 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. 启动开发环境

Terminal window
NODE_ENV=development pnpm start:dev

2. 启动生产环境

Terminal window
NODE_ENV=production pnpm start:prod

3. 配置验证失败示例

如果配置验证失败,会显示错误信息:

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=3000

6. 不要提交 .env 文件 🚫#

.gitignore 中添加:

# 环境变量文件
.env
.env.local
.env.development.local
.env.production.local
# 但保留示例文件
!.env.example

7. 配置命名空间 📦#

// ✅ 推荐:使用命名空间组织配置
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 后配置未更新

解决方案

Terminal window
# ✅ 确保环境变量正确设置
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】14 配置管理(Configuration)
https://blog.fridolph.top/posts/2025-12-01__nest14-config/
作者
Fridolph
发布于
2025-12-01
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Fridolph
热爱 Coding、音乐和羽毛球的 90 后全栈工程师
公告
欢迎访问我的小站 ^_^ 我是昇哥,热爱Coding,喜爱音乐、羽毛球和摄影的 90后全栈工程师
分类
标签

文章目录