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.

Leave a Comment

Your email address will not be published. Required fields are marked *