ConfigModule and .env Files

Never Hard-code Secrets

Database URLs, JWT secrets and API keys change between your laptop, staging and production. Keep them in environment variables, loaded from a .env file in development.

Example

npm i @nestjs/config

Example output

added 4 packages in 3s

The .env file

.env

PORT=3000
NODE_ENV=development
DATABASE_URL=postgresql://shopease:secret@localhost:5432/shopease
JWT_SECRET=change-me-in-production
RAZORPAY_KEY_ID=rzp_test_1DP5mmOlF5G5ag

Note: Add .env to .gitignore. Commit a .env.example with dummy values instead.

ConfigModule and ConfigService

src/app.module.ts

import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,                                     // no need to import it in every module
      envFilePath: ['.env.local', '.env'],                // first file wins
      cache: true,
    }),
  ],
})
export class AppModule {}

src/payments/razorpay.service.ts

import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class RazorpayService {
  private readonly keyId: string;

  constructor(private readonly config: ConfigService) {
    this.keyId = this.config.getOrThrow<string>('RAZORPAY_KEY_ID');   // throws if missing
  }

  port() {
    return this.config.get<number>('PORT', 3000);                      // default value
  }
}

Typed Config Namespaces

src/config/database.config.ts

import { registerAs } from '@nestjs/config';

export default registerAs('database', () => ({
  url: process.env.DATABASE_URL,
  poolSize: parseInt(process.env.DB_POOL_SIZE ?? '10', 10),
  ssl: process.env.NODE_ENV === 'production',
}));

// app.module.ts:  ConfigModule.forRoot({ isGlobal: true, load: [databaseConfig] })
// usage:          this.config.get<number>('database.poolSize')