This episode covers NestJS configuration management: the @nestjs/config module, configuration modules, per-environment configuration, validation schemas, externalized configuration using environment variables, and secrets management practices in development and production.

A production application should never have hard-coded configuration values — like database secrets or API keys — directly in the source code. Configuration must be separated from the code and managed per environment.
Episode 8 covers @nestjs/config, per-environment configuration, schema validation, externalized configuration, and how to manage secrets safely.
@nestjs/config is the official module that handles configuration. Install it first:
npm install @nestjs/configThen register it in the module:
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
],
})
export class AppModule {}With isGlobal: true, ConfigService can be injected anywhere without importing the module repeatedly.
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class AppConfigService {
constructor(private readonly configService: ConfigService) {}
getPort(): number {
return this.configService.get<number>("PORT", 3000);
}
getNodeEnv(): string {
return this.configService.get<string>("NODE_ENV", "development");
}
}configService.get("PORT", 3000) reads a value with a default fallback if it's not found.
ConfigModule.forRoot() reads the .env file in the project root. For different environments, we can use different files:
PORT=3000
DATABASE_URL=sqlite:data.sqlite
JWT_SECRET=rahasia-developmentFor production, values are injected from system environment variables or a secret manager, not from a committed .env file.
To group related configuration, create a factory function:
export default () => ({
port: parseInt(process.env.PORT ?? "3000", 10),
database: {
url: process.env.DATABASE_URL,
},
});Then point ConfigModule to this file:
ConfigModule.forRoot({
load: [configuration],
})Misconfiguration should be detected when the application starts, not at runtime. Use Joi for validation:
npm install joiimport * as Joi from "joi";
ConfigModule.forRoot({
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid("development", "production", "test")
.default("development"),
PORT: Joi.number().default(3000),
DATABASE_URL: Joi.string().required(),
}),
})If a required variable is missing, the application fails to start immediately with a clear message.
The principle of externalized configuration: the same code runs in every environment, and configuration values are injected from outside. This allows the same deploy to staging and production without changing the code.
PORT=8080 DATABASE_URL=postgres://user:pass@host/db npm run start:prodIn development, .env is enough. In production, secrets should be stored in a secret manager — like AWS Secrets Manager, HashiCorp Vault, or the built-in secrets feature of your deployment platform. Never put secrets in the repository, including a committed .env file.
.env
.env.local
.env.*.localWith the .gitignore above, local env files will never be committed to Git.
Episode 8 makes your application ready to move between environments without changing code: @nestjs/config, configuration modules, validation schemas, externalized configuration, and safe secrets practices.
Key takeaways:
ConfigModule.forRoot() reads the .env file and exposes ConfigService.isGlobal: true makes ConfigService injectable everywhere.In the next episode 9 we'll discuss logging, monitoring, and health — the built-in logger and custom logger service, Winston and Pino integration, health checks with @nestjs/terminus, and basic observability patterns for metrics.