11 min readUpdated May 24, 2025

Using Environment Variables in NestJS AppModule for Database Connections: A Complete Guide

Learn how to securely manage PostgreSQL connections in your NestJS app by using environment variables with @nestjs/config. This guide walks you through setting up a .env file, dynamically injecting values into TypeOrmModule, and validating your config with joi—all while avoiding hardcoded credentials and ensuring a clean, scalable setup.

Written by

TypeScriptNestJSReact, Vue and Typescript

Using Environment Variables in NestJS AppModule for Database Connections: A Complete Guide

When building a NestJS application with a database like PostgreSQL or MySQL, hardcoding connection details in your AppModule is a recipe for trouble—security risks, inflexibility, and maintenance headaches. Instead, environment variables are the way to go. In this post, inspired by a Stack Overflow question and the NestJS Configuration Documentation, I’ll show you how to use environment variables in your AppModule for TypeORM database connections, with a clean and secure setup.

Why Use Environment Variables?

Environment variables keep sensitive data like database credentials out of your codebase. They allow you to:

  • Secure your app: Avoid exposing passwords in version control.
  • Switch environments easily: Use different settings for development, staging, and production.
  • Simplify configuration: Update settings without changing code.

The challenge, as seen in the Stack Overflow post, is accessing these variables in AppModule, which is loaded early in a NestJS app. Let’s explore how to do this using the @nestjs/config package.

The Problem: Accessing Environment Variables in AppModule

A common approach is to use process.env directly in AppModule for TypeORM configuration, like this:

1import { Module } from '@nestjs/common'; 2import { TypeOrmModule } from '@nestjs/typeorm'; 3 4@Module({ 5 imports: [ 6 TypeOrmModule.forRoot({ 7 type: 'postgres', 8 host: process.env.DB_HOST, 9 port: Number(process.env.DB_PORT), 10 username: process.env.DB_USERNAME, 11 password: process.env.DB_PASSWORD, 12 database: process.env.DB_NAME, 13 autoLoadEntities: true, 14 synchronize: false, 15 }), 16 ], 17}) 18export class AppModule {}

However, if process.env.DB_HOST or other variables are undefined—perhaps because the .env file wasn’t loaded or parsed correctly—you’ll hit errors like failed database connections. The @nestjs/config package solves this by providing a robust configuration system.

Solution: Using @nestjs/config with TypeORM

The @nestjs/config package, as outlined in the NestJS documentation, lets you load environment variables from .env files, validate them, and inject them into your application. Here’s how to set it up.

Step 1: Install Dependencies

Install the required packages:

1npm install @nestjs/config @nestjs/typeorm typeorm pg

Step 2: Create a .env File

Create a .env file in your project root with your database credentials:

1DB_HOST=localhost 2DB_PORT=5432 3DB_USERNAME=your_username 4DB_PASSWORD=your_password 5DB_NAME=your_database

Step 3: Configure @nestjs/config in AppModule

In src/app.module.ts, use ConfigModule to load the .env file and ConfigService to access variables. Pass these to TypeOrmModule.forRootAsync for dynamic configuration.

1import { Module } from '@nestjs/common'; 2import { ConfigModule, ConfigService } from '@nestjs/config'; 3import { TypeOrmModule } from '@nestjs/typeorm'; 4 5@Module({ 6 imports: [ 7 ConfigModule.forRoot({ 8 isGlobal: true, // Makes ConfigModule available everywhere 9 envFilePath: '.env', // Path to .env file 10 }), 11 TypeOrmModule.forRootAsync({ 12 imports: [ConfigModule], 13 useFactory: (configService: ConfigService) => ({ 14 type: 'postgres', 15 host: configService.get<string>('DB_HOST'), 16 port: configService.get<number>('DB_PORT'), 17 username: configService.get<string>('DB_USERNAME'), 18 password: configService.get<string>('DB_PASSWORD'), 19 database: configService.get<string>('DB_NAME'), 20 autoLoadEntities: true, 21 synchronize: false, // Set to false in production 22 }), 23 inject: [ConfigService], 24 }), 25 ], 26}) 27export class AppModule {}
  • ConfigModule.forRoot({ isGlobal: true }) loads the .env file and makes ConfigService available globally.
  • TypeOrmModule.forRootAsync uses useFactory to dynamically configure TypeORM with environment variables.
  • inject: [ConfigService] ensures ConfigService is available in the factory.

Step 4: Add a Sample Entity and Module

Create a simple entity, e.g., src/users/user.entity.ts:

1import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 3@Entity() 4export class User { 5 @PrimaryGeneratedColumn() 6 id: number; 7 8 @Column() 9 name: string; 10}

Create a UsersModule in src/users/users.module.ts:

1import { Module } from '@nestjs/common'; 2import { TypeOrmModule } from '@nestjs/typeorm'; 3import { User } from './user.entity'; 4 5@Module({ 6 imports: [TypeOrmModule.forFeature([User])], 7}) 8export class UsersModule {}

Update AppModule to import UsersModule:

1import { Module } from '@nestjs/common'; 2import { ConfigModule, ConfigService } from '@nestjs/config'; 3import { TypeOrmModule } from '@nestjs/typeorm'; 4import { UsersModule } from './users/users.module'; 5 6@Module({ 7 imports: [ 8 ConfigModule.forRoot({ 9 isGlobal: true, 10 envFilePath: '.env', 11 }), 12 TypeOrmModule.forRootAsync({ 13 imports: [ConfigModule], 14 useFactory: (configService: ConfigService) => ({ 15 type: 'postgres', 16 host: configService.get<string>('DB_HOST'), 17 port: configService.get<number>('DB_PORT'), 18 username: configService.get<string>('DB_USERNAME'), 19 password: configService.get<string>('DB_PASSWORD'), 20 database: configService.get<string>('DB_NAME'), 21 autoLoadEntities: true, 22 synchronize: false, 23 }), 24 inject: [ConfigService], 25 }), 26 UsersModule, 27 ], 28}) 29export class AppModule {}

Step 5: Test the Setup

Run your PostgreSQL database locally (e.g., via Docker: docker run -e POSTGRES_USER=your_username -e POSTGRES_PASSWORD=your_password -e POSTGRES_DB=your_database -p 5432:5432 postgres).

Start your NestJS app:

1npm run start:dev

If configured correctly, the app connects to the database using the .env variables. You can verify by creating a controller to test the User entity.

Step 6: Validate Environment Variables (Optional)

To ensure variables are present and valid, use @nestjs/config’s validation feature with a library like joi. Install it:

1npm install joi

Update AppModule:

1import { Module } from '@nestjs/common'; 2import { ConfigModule, ConfigService } from '@nestjs/config'; 3import { TypeOrmModule } from '@nestjs/typeorm'; 4import * as Joi from 'joi'; 5import { UsersModule } from './users/users.module'; 6 7@Module({ 8 imports: [ 9 ConfigModule.forRoot({ 10 isGlobal: true, 11 envFilePath: '.env', 12 validationSchema: Joi.object({ 13 DB_HOST: Joi.string().required(), 14 DB_PORT: Joi.number().required(), 15 DB_USERNAME: Joi.string().required(), 16 DB_PASSWORD: Joi.string().required(), 17 DB_NAME: Joi.string().required(), 18 }), 19 }), 20 TypeOrmModule.forRootAsync({ 21 imports: [ConfigModule], 22 useFactory: (configService: ConfigService) => ({ 23 type: 'postgres', 24 host: configService.get<string>('DB_HOST'), 25 port: configService.get<number>('DB_PORT'), 26 username: configService.get<string>('DB_USERNAME'), 27 password: configService.get<string>('DB_PASSWORD'), 28 database: configService.get<string>('DB_NAME'), 29 autoLoadEntities: true, 30 synchronize: false, 31 }), 32 inject: [ConfigService], 33 }), 34 UsersModule, 35 ], 36}) 37export class AppModule {}

This ensures the app fails to start if required variables are missing or invalid, providing clear error messages.

Best Practices from NestJS Documentation

  • Use .env Files for Each Environment: Create .env.development, .env.production, etc., and specify them in ConfigModule.forRoot({ envFilePath: ['.env.development', '.env'] }) to load the appropriate file.
  • Make ConfigModule Global: Set isGlobal: true to avoid importing ConfigModule in every module.
  • Validate Environment Variables: Use joi or similar to enforce required variables and types.
  • Avoid synchronize in Production: Set synchronize: false in TypeORM to prevent accidental schema changes.
  • Secure Your .env File: Add .env to .gitignore to keep it out of version control.

Common Pitfalls

  • Missing .env File: Ensure the .env file exists and is correctly referenced in ConfigModule.
  • Incorrect Variable Types: Use configService.get<number>('DB_PORT') to cast types explicitly.
  • Early Loading Issues: Since AppModule loads early, ensure ConfigModule is initialized first with forRoot.
  • Environment Overrides: If variables are set in the system environment, they override .env file values.

Conclusion

Using environment variables in your NestJS AppModule for database connections is straightforward with @nestjs/config. By setting up a .env file, configuring ConfigModule, and using TypeOrmModule.forRootAsync, you can create a secure, flexible, and maintainable setup. Validation with joi adds an extra layer of robustness.

Have you tackled environment variables in your NestJS projects? Share your tips or questions in the comments below!

Happy coding, and keep building secure NestJS apps!

About

Software Developer & Consultant specializing in JavaScript, TypeScript, and modern web technologies.

Share this article