Skip to content

什么是配置系统?🤔 ​

配置系统是应用程序中管理各种配置项的重要组成部分。在不同环境(开发、测试、生产)中,应用程序可能需要不同的配置值。NestJS 提供了强大的配置管理功能,帮助我们更好地组织和管理这些配置。

Express vs NestJS 对比 🆚 ​

让我们看看在传统 Express 和 NestJS 中如何处理配置:

Express 写法:

javascript
// config.js
require('dotenv').config();

const config = {
  database: {
    host: process.env.DB_HOST,
    port: parseInt(process.env.DB_PORT, 10),
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
  },
  redis: {
    host: process.env.REDIS_HOST,
    port: parseInt(process.env.REDIS_PORT, 10),
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN,
  }
};

module.exports = config;

// 使用配置
const config = require('./config');
const db = require('mysql').createConnection(config.database);

NestJS 写法:

typescript
// config/configuration.ts
export default () => ({
  database: {
    host: process.env.DB_HOST,
    port: parseInt(process.env.DB_PORT, 10),
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
  },
  redis: {
    host: process.env.REDIS_HOST,
    port: parseInt(process.env.REDIS_PORT, 10),
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: process.env.JWT_EXPIRES_IN,
  }
});

// app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({
      load: [configuration],
      isGlobal: true,
    }),
  ],
})
export class AppModule {}

// users.service.ts
@Injectable()
export class UsersService {
  constructor(private configService: ConfigService) {
    const dbConfig = this.configService.get('database');
  }
}

配置模块的高级特性 🎯 ​

1. 环境变量验证 ​

使用 Joi 进行配置验证:

typescript
// config/validation.schema.ts
import * as Joi from 'joi';

export const validationSchema = Joi.object({
  NODE_ENV: Joi.string()
    .valid('development', 'production', 'test')
    .default('development'),
  PORT: Joi.number().default(3000),
  DB_HOST: Joi.string().required(),
  DB_PORT: Joi.number().required(),
  DB_USERNAME: Joi.string().required(),
  DB_PASSWORD: Joi.string().required(),
  JWT_SECRET: Joi.string().required(),
  JWT_EXPIRES_IN: Joi.string().default('1d'),
});

// app.module.ts
@Module({
  imports: [
    ConfigModule.forRoot({
      validationSchema,
      validationOptions: {
        allowUnknown: true,
        abortEarly: false,
      },
    }),
  ],
})
export class AppModule {}

2. 自定义配置文件 ​

typescript
// config/database.config.ts
import { registerAs } from '@nestjs/config';

export default registerAs('database', () => ({
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT, 10),
  username: process.env.DB_USERNAME,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
  entities: ['dist/**/*.entity{.ts,.js}'],
  synchronize: process.env.NODE_ENV !== 'production',
}));

// config/redis.config.ts
export default registerAs('redis', () => ({
  host: process.env.REDIS_HOST,
  port: parseInt(process.env.REDIS_PORT, 10),
  password: process.env.REDIS_PASSWORD,
  db: parseInt(process.env.REDIS_DB, 10),
}));

3. 配置命名空间 ​

typescript
@Module({
  imports: [
    ConfigModule.forRoot({
      load: [databaseConfig, redisConfig, jwtConfig],
      isGlobal: true,
    }),
  ],
})
export class AppModule {}

@Injectable()
export class AppService {
  constructor(private configService: ConfigService) {
    // 使用命名空间访问配置
    const dbConfig = this.configService.get('database');
    const redisConfig = this.configService.get('redis');
    const jwtConfig = this.configService.get('jwt');
  }
}

实战示例 💡 ​

1. 动态模块配置 ​

typescript
// database/database.module.ts
@Module({})
export class DatabaseModule {
  static forRoot(options?: DatabaseModuleOptions): DynamicModule {
    return {
      module: DatabaseModule,
      imports: [
        TypeOrmModule.forRootAsync({
          imports: [ConfigModule],
          inject: [ConfigService],
          useFactory: (configService: ConfigService) => ({
            type: 'mysql',
            host: configService.get('database.host'),
            port: configService.get('database.port'),
            username: configService.get('database.username'),
            password: configService.get('database.password'),
            database: configService.get('database.name'),
            entities: [__dirname + '/**/*.entity{.ts,.js}'],
            synchronize: configService.get('database.synchronize'),
            ...options,
          }),
        }),
      ],
    };
  }
}

2. 环境特定配置 ​

typescript
// config/envs/development.env
DB_HOST=localhost
DB_PORT=3306
REDIS_HOST=localhost
REDIS_PORT=6379

// config/envs/production.env
DB_HOST=production-db
DB_PORT=3306
REDIS_HOST=production-redis
REDIS_PORT=6379

// main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  const configService = app.get(ConfigService);
  const port = configService.get('PORT');
  
  await app.listen(port);
}

3. 配置服务工厂 ​

typescript
// config/config.service.ts
@Injectable()
export class AppConfigService {
  constructor(private configService: ConfigService) {}

  get isDevelopment(): boolean {
    return this.configService.get('NODE_ENV') === 'development';
  }

  get isProduction(): boolean {
    return this.configService.get('NODE_ENV') === 'production';
  }

  get databaseUrl(): string {
    const { username, password, host, port, database } = 
      this.configService.get('database');
    return `mysql://${username}:${password}@${host}:${port}/${database}`;
  }

  get jwtConfig() {
    return {
      secret: this.configService.get('jwt.secret'),
      expiresIn: this.configService.get('jwt.expiresIn'),
    };
  }
}

最佳实践 ✨ ​

1. 配置分层 ​

typescript
// 基础配置
const baseConfig = {
  app: {
    name: 'NestJS App',
    description: 'A NestJS application',
  },
};

// 环境特定配置
const envConfig = {
  development: {
    debug: true,
    swagger: true,
  },
  production: {
    debug: false,
    swagger: false,
  },
};

// 合并配置
export default () => ({
  ...baseConfig,
  ...envConfig[process.env.NODE_ENV || 'development'],
});

2. 敏感信息处理 ​

typescript
// config/sensitive.config.ts
export default registerAs('sensitive', () => ({
  database: {
    password: process.env.DB_PASSWORD,
    encryption_key: process.env.DB_ENCRYPTION_KEY,
  },
  jwt: {
    secret: process.env.JWT_SECRET,
    refresh_secret: process.env.JWT_REFRESH_SECRET,
  },
}));

// 使用加密服务
@Injectable()
export class EncryptionService {
  constructor(private configService: ConfigService) {
    // 确保敏感配置只在需要时访问
    this.encryptionKey = this.configService.get('sensitive.database.encryption_key');
  }
}

总结 📝 ​

通过这篇文章,我们深入学习了:

  1. 配置系统的基本概念
  2. 不同类型的配置管理方式
  3. 环境变量的处理和验证
  4. 配置的最佳实践
  5. 敏感信息的处理方法

NestJS 的配置系统提供了:

  • 类型安全的配置访问
  • 环境特定的配置管理
  • 配置验证
  • 模块化的配置组织
  • 敏感信息的安全处理

相关资源 📚 ​