4.3 KiB
4.3 KiB
title, impact, impactDescription, tags
| title | impact | impactDescription | tags |
|---|---|---|---|
| Use ConfigModule for Environment Configuration | LOW-MEDIUM | Proper configuration prevents deployment failures | 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):
// 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):
// 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