typescriptpostgresqlenvironment-variablesnestjstypeorm

NestJS TypeORM Environment Variables Not Loading Despite dotenv Configuration


I'm working on a NestJS application with TypeORM and PostgreSQL. My environment variables are not being loaded when the application starts, even though I can see dotenv injecting them in the console output. This results in a "password authentication failed" error because the database configuration receives undefined values. Here's what I see in the logs:

[dotenv@17.0.0] injecting env (5) from .env – πŸ” encrypt with dotenvx: https://dotenvx.com
[Nest] 11840  - 06/29/2025, 4:18:13 PM     LOG [NestFactory] Starting Nest application...
[Nest] 11840  - 06/29/2025, 4:18:13 PM   ERROR [TypeOrmModule] Unable to connect to the database. Retrying (1)...
error: password authentication failed for user "user"

When I debug the environment variables in my app.module.ts, they all show as undefined: Raw Environment Variables: POSTGRES_HOST: undefined POSTGRES_PORT: undefined POSTGRES_USER: undefined POSTGRES_PASSWORD: undefined POSTGRES_DATABASE: undefined All env keys containing POSTGRES: [] Current Configuration My .env file (located in project root): env

POSTGRES_HOST=localhost POSTGRES_PORT=5433 POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres POSTGRES_DATABASE=course-management

My app.module.ts: typescript

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { CourseModule } from './course/course.module';
import { ScoreModule } from './score/score.module';

// Debug logging
console.log('Environment Variables:', {
  host: process.env.POSTGRES_HOST,
  port: process.env.POSTGRES_PORT,
  user: process.env.POSTGRES_USER,
  database: process.env.POSTGRES_DATABASE,
  passwordSet: !!process.env.POSTGRES_PASSWORD
});

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.POSTGRES_HOST || 'localhost',
      port: parseInt(process.env.POSTGRES_PORT || '5433'),
      password: String(process.env.POSTGRES_PASSWORD || ''),
      username: process.env.POSTGRES_USER || 'postgres',
      entities: [],
      database: process.env.POSTGRES_DATABASE || 'course-management',
      synchronize: true,
      logging: true,
    }),
    CourseModule,
    ScoreModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

My main.ts: typescript

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as dotenv from 'dotenv';

dotenv.config();

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

What I Expected vs What Happened Expected: The environment variables should be loaded and available when app.module.ts is evaluated, allowing TypeORM to connect to PostgreSQL with the correct credentials. What Actually Happened:

dotenv shows it's injecting 5 variables from .env But when app.module.ts tries to read them, they're all undefined TypeORM tries to connect with default/undefined values Connection fails with "password authentication failed for user 'user'" (should be 'postgres')

What I've Tried

Verified .env file location: It's in the project root directory Checked .env file format: No spaces around equals signs Added explicit string conversion: String(process.env.POSTGRES_PASSWORD) Added debug logging: Confirms all variables are undefined Installed dotenv and types: npm install dotenv @types/dotenv Verified database credentials: Can connect manually with psql using the same credentials

The issue seems to be a timing problem where app.module.ts is evaluated before the environment variables are loaded, despite calling dotenv.config() in main.ts. Questions

Why are the environment variables undefined in app.module.ts even though dotenv claims to inject them? What's the correct way to ensure environment variables are available during module initialization in NestJS? Is there a better pattern than using dotenv directly with NestJS and TypeORM?

Environment:

NestJS 10.x TypeORM PostgreSQL dotenv 17.0.0 Node.js (Windows)


Solution

  • I solved this by switching to @nestjs/config, which handles .env loading properly and integrates well with TypeORM using async configuration.

    Previously, I used dotenv.config() in main.ts, but that ran too late. By the time it executed, AppModule had already been evaluated, and all my environment variables were still undefined. This caused TypeORM to attempt connecting with missing credentials.

    Here’s the setup that worked for me

    import { Module } from '@nestjs/common';
    import { ConfigModule, ConfigService } from '@nestjs/config';
    import { TypeOrmModule } from '@nestjs/typeorm';
    
    @Module({
      imports: [
        ConfigModule.forRoot({
          isGlobal: true,
          envFilePath: '.env',
        }),
        TypeOrmModule.forRootAsync({
          inject: [ConfigService],
          useFactory: (config: ConfigService) => ({
            type: 'postgres',
            host: config.get<string>('POSTGRES_HOST'),
            port: config.get<number>('POSTGRES_PORT', 5432),
            username: config.get<string>('POSTGRES_USER'),
            password: config.get<string>('POSTGRES_PASSWORD'),
            database: config.get<string>('POSTGRES_DATABASE'),
            synchronize: config.get<boolean>('TYPEORM_SYNCHRONIZE', false),
            autoLoadEntities: true,
            logging: config.get<boolean>('TYPEORM_LOGGING', false),
          }),
        }),
      ],
    })
    export class AppModule {}
    

    This setup ensures that all environment variables are loaded at the framework level before any modules or decorators attempt to use them. As a result, TypeORM now initializes with the correct database credentials, and none of the environment variables are undefined at runtime.