第 183 章:配置管理(ConfigModule)
学习目标
- 使用 @nestjs/config 管理配置
- 学会 .env 与多环境配置
- 掌握配置校验
- 学会自定义配置文件
一、安装
bash
pnpm add @nestjs/config二、基本使用
2.1 注册
typescript
// app.module.ts
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true, // 全局可用,无需重复 imports
cache: true, // 缓存读取结果
envFilePath: '.env', // 默认就是这个
}),
],
})
export class AppModule {}2.2 注入使用
typescript
@Injectable()
export class AppService {
constructor(private config: ConfigService) {}
getDbUrl(): string {
return this.config.get('DATABASE_URL');
}
getPort(): number {
return this.config.get('PORT', 3000); // 默认值
}
getBool(key: string): boolean {
return this.config.get(key) === 'true';
}
}三、.env 文件
env
# .env
NODE_ENV=development
PORT=3000
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USER=admin
DATABASE_PASS=secret
DATABASE_NAME=myapp
JWT_SECRET=super-secret
JWT_EXPIRES_IN=7d不要提交 .env
.gitignore 加入 .env,只提交 .env.example!
四、多环境配置
4.1 文件结构
config/
├── .env.development
├── .env.production
├── .env.test
└── .env.example4.2 加载配置
typescript
ConfigModule.forRoot({
envFilePath: [
`.env.${process.env.NODE_ENV}`,
'.env', // 兜底
],
isGlobal: true,
}),4.3 自定义配置
typescript
// src/config/database.config.ts
export default () => ({
database: {
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10),
user: process.env.DATABASE_USER,
pass: process.env.DATABASE_PASS,
name: process.env.DATABASE_NAME,
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN,
},
});
// app.module.ts
ConfigModule.forRoot({
load: [databaseConfig],
isGlobal: true,
}),4.4 命名空间
typescript
// database.config.ts
export default registerAs('database', () => ({
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10),
}));
// jwt.config.ts
export default registerAs('jwt', () => ({
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN,
}));
// 使用
ConfigModule.forRoot({
load: [databaseConfig, jwtConfig],
});
// 注入
constructor(private config: ConfigService) {}
// 方式一
this.config.get('database.host');
this.config.get('jwt.secret');
// 方式二(强类型)
constructor(
@Inject(databaseConfig.KEY)
private dbConfig: ConfigType<typeof databaseConfig>,
) {}
this.dbConfig.host;五、配置校验
5.1 安装
bash
pnpm add joi5.2 校验 .env
typescript
import * as Joi from 'joi';
ConfigModule.forRoot({
validationSchema: Joi.object({
NODE_ENV: Joi.string().valid('development', 'production', 'test').default('development'),
PORT: Joi.number().default(3000),
DATABASE_HOST: Joi.string().required(),
DATABASE_PORT: Joi.number().default(5432),
DATABASE_USER: Joi.string().required(),
DATABASE_PASS: Joi.string().required(),
DATABASE_NAME: Joi.string().required(),
JWT_SECRET: Joi.string().min(10).required(),
JWT_EXPIRES_IN: Joi.string().default('7d'),
}),
validationOptions: {
abortEarly: false, // 一次报所有错
},
}),5.3 校验失败
Error: Config validation error: "JWT_SECRET" is required六、自定义校验函数
typescript
ConfigModule.forRoot({
validate: (env) => {
const errors = [];
if (!env.JWT_SECRET) errors.push('JWT_SECRET is required');
if (errors.length > 0) {
throw new Error(errors.join('; '));
}
return {
PORT: parseInt(env.PORT, 10) || 3000,
DATABASE_URL: env.DATABASE_URL,
JWT_SECRET: env.JWT_SECRET,
};
},
}),七、在不同模块使用
7.1 TypeOrmModule
typescript
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres',
host: config.get('DATABASE_HOST'),
port: config.get('DATABASE_PORT'),
username: config.get('DATABASE_USER'),
password: config.get('DATABASE_PASS'),
database: config.get('DATABASE_NAME'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: config.get('NODE_ENV') !== 'production',
}),
})7.2 JwtModule
typescript
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get('JWT_SECRET'),
signOptions: { expiresIn: config.get('JWT_EXPIRES_IN') },
}),
})八、配置中心(进阶)
typescript
// config.service.ts - 自定义配置服务
@Injectable()
export class ConfigCenterService {
constructor(private config: ConfigService) {}
get database() {
return {
host: this.config.get('DATABASE_HOST'),
port: this.config.get('DATABASE_PORT'),
get url() {
return `postgres://${this.user}:${this.pass}@${this.host}:${this.port}/${this.name}`;
},
user: this.config.get('DATABASE_USER'),
pass: this.config.get('DATABASE_PASS'),
name: this.config.get('DATABASE_NAME'),
};
}
get jwt() {
return {
secret: this.config.get('JWT_SECRET'),
expiresIn: this.config.get('JWT_EXPIRES_IN'),
};
}
get server() {
return {
port: this.config.get('PORT', 3000),
env: this.config.get('NODE_ENV'),
isProd: this.config.get('NODE_ENV') === 'production',
};
}
}九、密钥管理
9.1 .env.example
env
NODE_ENV=development
PORT=3000
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_USER=
DATABASE_PASS=
DATABASE_NAME=myapp
JWT_SECRET=change-me
JWT_EXPIRES_IN=7d9.2 Docker Secret
yaml
# docker-compose.yml
services:
app:
environment:
- DATABASE_PASS_FILE=/run/secrets/db_pass
secrets:
- db_pass
secrets:
db_pass:
file: ./secrets/db_pass.txttypescript
// main.ts
import fs from 'fs';
function loadSecrets() {
if (fs.existsSync('/run/secrets/db_pass')) {
process.env.DATABASE_PASS = fs.readFileSync('/run/secrets/db_pass', 'utf8').trim();
}
}
loadSecrets();
bootstrap();十、本章小结
| API | 用途 |
|---|---|
forRoot() | 初始化模块 |
forFeature() | 子模块使用 |
load | 加载自定义配置 |
validate | 校验配置 |
validationSchema | Joi schema 校验 |
registerAs | 命名空间 |
ConfigType | 强类型 |
envFilePath | 环境变量文件 |
动手练习
- 创建
.env与.env.example - 用 Joi 校验所有必需字段
- 把 database 与 jwt 配置拆分到不同命名空间
- 在 TypeORM 中读取 ConfigService
推荐阅读
- 📖 Configuration
- 📖 Joi
下一章:第 184 章:日志系统(Logger) →