Skip to content
第 183 / 250 章Node⏱ 12 分钟阅读

第 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.example

4.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 joi

5.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=7d

9.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.txt
typescript
// 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校验配置
validationSchemaJoi schema 校验
registerAs命名空间
ConfigType强类型
envFilePath环境变量文件

动手练习

  1. 创建 .env.env.example
  2. 用 Joi 校验所有必需字段
  3. 把 database 与 jwt 配置拆分到不同命名空间
  4. 在 TypeORM 中读取 ConfigService

推荐阅读


下一章:第 184 章:日志系统(Logger)

本站基于 VitePress 构建 · 由 Codebook 团队维护