什么是配置系统?🤔
配置系统是应用程序中管理各种配置项的重要组成部分。在不同环境(开发、测试、生产)中,应用程序可能需要不同的配置值。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');
}
}总结 📝
通过这篇文章,我们深入学习了:
- 配置系统的基本概念
- 不同类型的配置管理方式
- 环境变量的处理和验证
- 配置的最佳实践
- 敏感信息的处理方法
NestJS 的配置系统提供了:
- 类型安全的配置访问
- 环境特定的配置管理
- 配置验证
- 模块化的配置组织
- 敏感信息的安全处理