168 lines
4.3 KiB
Markdown
168 lines
4.3 KiB
Markdown
---
|
|
title: Use ConfigModule for Environment Configuration
|
|
impact: LOW-MEDIUM
|
|
impactDescription: Proper configuration prevents deployment failures
|
|
tags: devops, configuration, environment, validation
|
|
---
|
|
|
|
## Use ConfigModule for Environment Configuration
|
|
|
|
Use `@nestjs/config` for environment-based configuration. Validate configuration at startup to fail fast on misconfigurations. Use namespaced configuration for organization and type safety.
|
|
|
|
**Incorrect (accessing process.env directly):**
|
|
|
|
```typescript
|
|
// Access process.env directly
|
|
@Injectable()
|
|
export class DatabaseService {
|
|
constructor() {
|
|
// No validation, can fail at runtime
|
|
this.connection = new Pool({
|
|
host: process.env.DB_HOST,
|
|
port: parseInt(process.env.DB_PORT), // NaN if missing
|
|
password: process.env.DB_PASSWORD, // undefined if missing
|
|
});
|
|
}
|
|
}
|
|
|
|
// Scattered env access
|
|
@Injectable()
|
|
export class EmailService {
|
|
sendEmail() {
|
|
// Different services access env differently
|
|
const apiKey = process.env.SENDGRID_API_KEY || 'default';
|
|
// Typos go unnoticed: process.env.SENDGRID_API_KY
|
|
}
|
|
}
|
|
```
|
|
|
|
**Correct (use @nestjs/config with validation):**
|
|
|
|
```typescript
|
|
// Setup validated configuration
|
|
import { ConfigModule, ConfigService, registerAs } from '@nestjs/config';
|
|
import * as Joi from 'joi';
|
|
|
|
// config/database.config.ts
|
|
export const databaseConfig = 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,
|
|
}));
|
|
|
|
// config/app.config.ts
|
|
export const appConfig = registerAs('app', () => ({
|
|
port: parseInt(process.env.PORT, 10) || 3000,
|
|
environment: process.env.NODE_ENV || 'development',
|
|
apiPrefix: process.env.API_PREFIX || 'api',
|
|
}));
|
|
|
|
// config/validation.schema.ts
|
|
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().default(5432),
|
|
DB_USERNAME: Joi.string().required(),
|
|
DB_PASSWORD: Joi.string().required(),
|
|
DB_NAME: Joi.string().required(),
|
|
JWT_SECRET: Joi.string().min(32).required(),
|
|
REDIS_URL: Joi.string().uri().required(),
|
|
});
|
|
|
|
// app.module.ts
|
|
@Module({
|
|
imports: [
|
|
ConfigModule.forRoot({
|
|
isGlobal: true, // Available everywhere without importing
|
|
load: [databaseConfig, appConfig],
|
|
validationSchema,
|
|
validationOptions: {
|
|
abortEarly: true, // Stop on first error
|
|
allowUnknown: true, // Allow other env vars
|
|
},
|
|
}),
|
|
TypeOrmModule.forRootAsync({
|
|
inject: [ConfigService],
|
|
useFactory: (config: ConfigService) => ({
|
|
type: 'postgres',
|
|
host: config.get('database.host'),
|
|
port: config.get('database.port'),
|
|
username: config.get('database.username'),
|
|
password: config.get('database.password'),
|
|
database: config.get('database.database'),
|
|
autoLoadEntities: true,
|
|
}),
|
|
}),
|
|
],
|
|
})
|
|
export class AppModule {}
|
|
|
|
// Type-safe configuration access
|
|
export interface AppConfig {
|
|
port: number;
|
|
environment: 'development' | 'production' | 'test';
|
|
apiPrefix: string;
|
|
}
|
|
|
|
export interface DatabaseConfig {
|
|
host: string;
|
|
port: number;
|
|
username: string;
|
|
password: string;
|
|
database: string;
|
|
}
|
|
|
|
// Type-safe access
|
|
@Injectable()
|
|
export class AppService {
|
|
constructor(private config: ConfigService) {}
|
|
|
|
getPort(): number {
|
|
// Type-safe with generic
|
|
return this.config.get<number>('app.port');
|
|
}
|
|
|
|
getDatabaseConfig(): DatabaseConfig {
|
|
return this.config.get<DatabaseConfig>('database');
|
|
}
|
|
}
|
|
|
|
// Inject namespaced config directly
|
|
@Injectable()
|
|
export class DatabaseService {
|
|
constructor(
|
|
@Inject(databaseConfig.KEY)
|
|
private dbConfig: ConfigType<typeof databaseConfig>,
|
|
) {
|
|
// Full type inference!
|
|
const host = this.dbConfig.host; // string
|
|
const port = this.dbConfig.port; // number
|
|
}
|
|
}
|
|
|
|
// Environment files support
|
|
ConfigModule.forRoot({
|
|
envFilePath: [
|
|
`.env.${process.env.NODE_ENV}.local`,
|
|
`.env.${process.env.NODE_ENV}`,
|
|
'.env.local',
|
|
'.env',
|
|
],
|
|
});
|
|
|
|
// .env.development
|
|
// DB_HOST=localhost
|
|
// DB_PORT=5432
|
|
|
|
// .env.production
|
|
// DB_HOST=prod-db.example.com
|
|
// DB_PORT=5432
|
|
```
|
|
|
|
Reference: [NestJS Configuration](https://docs.nestjs.com/techniques/configuration)
|