NestJS Config Module
The Config Module manages environment variables in a structured, type-safe way. Instead of calling process.env.VARIABLE scattered across your codebase, you inject a ConfigService that reads, validates, and provides all configuration values in one place. This makes configuration consistent, testable, and easy to change per environment.
Why Configuration Management Matters
Without ConfigModule: database.ts → process.env.DB_HOST auth.ts → process.env.JWT_SECRET mail.ts → process.env.SMTP_HOST app.ts → process.env.PORT Problems: - No validation (missing vars cause runtime errors) - No type safety (always strings) - Hard to test (must set env vars in tests) - No central documentation of required variables With ConfigModule: One source of truth, validated at startup, injected anywhere.
Installing the Config Module
npm install @nestjs/config
Basic Setup
Import ConfigModule in the root module. Set isGlobal: true so you can inject ConfigService anywhere without importing ConfigModule in every feature module:
// app.module.ts
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
],
})
export class AppModule {}
Create a .env file in the project root:
# .env PORT=3000 DB_HOST=localhost DB_PORT=5432 DB_USER=postgres DB_PASS=secret DB_NAME=myapp JWT_SECRET=supersecretkey JWT_EXPIRES_IN=1h
Using ConfigService
@Injectable()
export class DatabaseService {
constructor(private configService: ConfigService) {}
getConnectionOptions() {
return {
host: this.configService.get<string>('DB_HOST'),
port: this.configService.get<number>('DB_PORT'),
username: this.configService.get<string>('DB_USER'),
password: this.configService.get<string>('DB_PASS'),
database: this.configService.get<string>('DB_NAME'),
};
}
}
Configuration Namespacing
For large applications, group related variables into named configuration objects using factory functions:
// 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_USER,
password: process.env.DB_PASS,
name: process.env.DB_NAME,
}));
// config/jwt.config.ts
export default registerAs('jwt', () => ({
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '1h',
}));
Load these namespaced configs in the module:
ConfigModule.forRoot({
isGlobal: true,
load: [databaseConfig, jwtConfig],
}),
Access them through the ConfigService with the namespace prefix:
const dbHost = this.configService.get<string>('database.host');
const jwtSecret = this.configService.get<string>('jwt.secret');
Validating Environment Variables at Startup
Missing or incorrectly formatted environment variables cause bugs that only appear at runtime — often in production. Use joi or zod to validate all variables when the application starts:
npm install joi
// app.module.ts
import * as Joi from 'joi';
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
PORT: Joi.number().default(3000),
DB_HOST: Joi.string().required(),
DB_PORT: Joi.number().required(),
DB_USER: Joi.string().required(),
DB_PASS: Joi.string().required(),
DB_NAME: Joi.string().required(),
JWT_SECRET: Joi.string().min(32).required(),
JWT_EXPIRES_IN: Joi.string().default('1h'),
}),
}),
If any required variable is missing or invalid, the application refuses to start and prints a clear error message. This is called fail-fast behavior — catching problems immediately instead of discovering them later when a specific feature is used.
Multiple .env Files Per Environment
ConfigModule.forRoot({
envFilePath: [`.env.${process.env.NODE_ENV}`, '.env'],
}),
Files loaded (in order, later files override earlier ones): .env.development ← loaded first when NODE_ENV=development .env ← always loaded as base configuration
Config in main.ts
Before the application starts fully, you sometimes need a config value in main.ts — for example, the port number. Since ConfigService requires the DI container, retrieve it after the app is created:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
const port = configService.get<number>('PORT') || 3000;
await app.listen(port);
console.log(`Application running on port ${port}`);
}
The Config Module turns scattered process.env calls into a structured, validated, type-safe configuration layer. When a new developer joins your team, they know exactly which environment variables the application needs — the validation schema acts as the official documentation for all required configuration.
