Guide to NestJS env variables with ConfigModule and dotenv

Posted November 4, 2023
NestJS env environment variables with NestJS ConfigModule & dotenv

This guide teaches how to use Nest.js env files to manage environment variables using the Nest.js Config Module and dotenv. Nest.js Config allows you to create a configuration module and a service to handle environment variables across your application.

In summary, you will learn the following:

  • How to create Nest.js env configuration module.
  • Using custom NestJS env configuration files for development and production.
  • Validating environment variables.
  • Using ConfigService in providers, modules, and in main.ts.

Setting up Nest.js Environment Variable using .env and ConfigModule

Environment variables allow you to configure your application’s behavior based on the environment it’s running.

You will need the @nestjs/config package to load these variables. Go ahead and install it:

npm install @nestjs/config

How to Add the ConfigModule to NestJS

First, ensure you’ve created a sample .env file within your NestJS app. For example:

DATABASE_HOST=localhost
DATABASE_PORT=5432
API_URL=https://api.com
PORT=3000
API_TOKEN=ThisIsSecteKey

Now, you will need a configuration setup load environment variable using @nestjs/config and make it available throughout your app.

Navigate to your app.module.ts and add AppModule for loading Config as follows:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule} from '@nestjs/config';

@Module({
  imports: [ConfigModule.forRoot()],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

In this case, @nestjs/config will use dotenv packages under the hood and ensure your app loads any .env file in the root of the project.

A KET NOTE: If you have any imports that use the environment variable from the .env file, the ConfigModule import must get imported first so those imports can access the env variables.

Loading the NestJS Environment Variables in .env

You have the .env file ready and ConfigModule ready. To load your variables, your app will use forRoot to load the environment Variables file path as follows:

Go back to your NestConfigModule.forRoot() in the app.module.ts file and use envFilePath to pass the .env file name as follows:

NestConfigModule.forRoot({
  envFilePath: '.env',
})

Your new import should now look as follows:

imports: [NestConfigModule.forRoot({
  envFilePath: '.env',
})],

Note: This env configuration must be imported again to any other modules in your application. However, you can avoid this repetitive step and use ConfigModule as Global using isGlobal: true as follows:

imports: [NestConfigModule.forRoot({
  envFilePath: '.env',
  isGlobal: true
})],

Using NestJS Environment Variables in Service Providers

To use the variables of your .env file. Navigate to your app.service.ts provider (Or your controller files):

The first step is to import @nestjs/config and then read Environment Variables using the ConfigService as follows:

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

@Injectable()
export class AppService {
  constructor(private readonly configService: ConfigService) {}
  
  getHello(): string {
    return this.configService.get('API_TOKEN')
  }
}

Or

  @Get()
  getHello(): string {
    const port = this.configService.getNumber('PORT');
    const databaseHost = this.configService.get('DATABASE_HOST');
    const databasePort = this.configService.get('DATABASE_PORT');

    return `Server is running on port ${port}, connected to ${databaseHost}:${databasePort}`;
  }

Using process.env to access environment variables in your NestJS Apps

If you want to use process.env to access environment variables in your NestJS service, you will update your example as follows:

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

@Injectable()
export class AppService {
  getHello(): string {
    const apiToken = process.env.API_TOKEN;
    
    if (apiToken) {
      return `API Token: ${apiToken}`;
    } else {
      return 'API Token is not defined.';
    }
  }
}

Below is another example of the environment variable that loads Nest.js PostgreSQL database configuration into your NestJS application:

  • Create the .env with environment variables for your PostgreSQL database:
DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=mydbuser
DB_PASSWORD=mypassword
DB_NAME=mydbname
  • Create Configuration module config.service.ts should remain the same with:
// src/config/config.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [ConfigModule.forRoot({
      envFilePath: '.env',
      isGlobal: true
    })],
  exports: [ConfigModule],
})
export class AppConfigModule {}
  • For the configuration provider, create a config.service.ts file. Here is how you can manage environment variables for your PostgreSQL database configurations:
// src/config/config.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService as NestConfigService } from '@nestjs/config';

@Injectable()
export class ConfigService {
  constructor(private nestConfigService: NestConfigService) {}
}

Now, integrate the ConfigService and PostgreSQL database configuration into your NestJS application in your app.module.ts and access variables as follows:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppConfigModule } from './config/config.module';
import { AppController } from './app.controller';
import { ConfigService } from './config/config.service';

@Module({
  imports: [
    AppConfigModule,
    TypeOrmModule.forRootAsync({
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get('DB_HOST'),
        port: configService.getNumber('DB_PORT'),
        username: configService.get('DB_USERNAME'),
        password: configService.get('DB_PASSWORD'),
        database: configService.get('DB_NAME'),
        entities: [__dirname + '/**/*.entity{.ts,.js}'],
        synchronize: true,
      }),
    }),
  ],
  controllers: [AppController],
})
export class AppModule {}

This example should work as follows if you want to use process.env to access the environment variables.

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: process.env.DB_HOST,
      port: parseInt(process.env.DB_PORT, 10),
      username: process.env.DB_USERNAME,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
  ],
  controllers: [AppController],
})
export class AppModule {}

Validating environment variables

You can pass custom validations to make sure the variable being read is indeed what it’s supposed to be.

For example, if the PORT variable is mandatory and can’t be empty of it must be a number, then validation will be a factor to your ConfigModule.

A simple way is to add validation to your provider (app.service.ts) as follows:

// src/config/config.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AppService {
  constructor(private readonly configService: ConfigService) {

    const requiredVariables = ['PORT', 'DATABASE_HOST', 'DATABASE_PORT'];

    for (const variable of requiredVariables) {
      if (!this.configService.get(variable)) {
        throw new Error(`Missing required environment variable: ${variable}`);
      }
    }
  }
  // The rest of the class remains the same
}

When working with Nest.js, class-validator and class-transformer are the right dependencies for validations. In this case, you will need to install them as follows:

npm install class-validator class-transformer

Then create a new file with constraints for each variable while using decorators from class-validator. Create a env.validation.ts file as follows:

import { IsInt, IsString, IsPositive } from 'class-validator';
import { Transform } from 'class-transformer';

export class envValidation {
  @IsInt()
  @IsPositive()
  // Transform environment variables
  @Transform(({ value }) => parseInt(value))
  PORT: number;

  @IsString()
  DATABASE_HOST: string;

  @IsInt()
  @IsPositive()
  @Transform(({ value }) => parseInt(value))
  DATABASE_PORT: number;
}

To use your Validations, you will need to edit your app.module.ts file and add envValidation() as follows:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule} from '@nestjs/config';
// Import your custom validation module
import { envValidation } from '../env.validation'; 

@Module({
  imports: [

    ConfigModule.forRoot({
      // This makes the config module global
      isGlobal: true, 
    }),
    // Include your custom envValidation
    envValidation, 
    ],

  controllers: [AppController],
  providers: [AppService],
})

export class AppModule {}

This way, the way you use your NestJS variables remains the same, but this time with an added validation layer.

Using ConfigModule with production and development .env files

Consider an example application that you want to run on your local development machine and still use the same app for production. In this case, you need to pick those values depending on the environment you are working on.

Take an example of a database value. In this case, you want two env files for both production and development. You will first use NODE_ENV to create your NPM scripts based on your environment. In this case, assume you have the development.env, and production.env files ready so you can update your scripts as follows:

"scripts": {
  "start:dev": "NODE_ENV=development npm run start",
  "start:prod": "NODE_ENV=production npm run start"
}

Or if you have difficulties telling Nest.js which environment to load, you can use cross-env as follows:

npm install -D cross-env
"scripts": {
  "start:dev": "cross-env NODE_ENV=development npm run start",
  "start:prod": "cross-env NODE_ENV=production npm run start"
}

Go ahead and use your ConfigModule just like we have described in this guide for either ConfigService or process.env.

Using custom configuration files

What if you are using a custom configuration file to load environment variables? Here is an example of a simple custom configuration file.

First, create a config.ts file, then add your custom configurations using load as follows:

export default () => ({
  port: parseInt(process.env.PORT) || 3000,
  api: {
    token: process.env.API_TOKEN,
    httpTimeout: 1000,
  },
  postgres: {
    database: {
      host: process.env.PG_HOST || 'localhost',
      port: parseInt(process.env.PG_PORT, 10) || 5432,
      username: process.env.PG_USERNAME || 'your_username',
      password: process.env.PG_PASSWORD || 'your_password',
      databaseName: process.env.PG_DB_NAME || 'your_database_name',
    },
  },
});

Now, this example will require to update the app.module.ts file as follows:

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import config from '../config'

@Module({
  imports: [ConfigModule.forRoot({
      isGlobal: true,
      load: [config]
    }
  )],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Your providers (service file) remain the same.

Using ConfigService in main.ts

When deploying your Nest.js app to run on production, you must dynamically set the port your app will use. The .env file is suitable for doing so. Therefore, you will be required to load it in your main.ts file as follows:

// Add ConfigService import
import { ConfigService } from '@nestjs/config';

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

  // Get the ConfigService instance
  const configService = app.get(ConfigService); 
  const port = configService.get<number>('PORT') || 3000;
  await app.listen(port);
}
bootstrap();

await app.listen(process.env.PORT); will still work here. But make sure you add parseInt to confirm your port is an integer. For example:

await app.listen(process.env.PORT ? parseInt(process.env.PORT) : 3000);

Conclusion

This guide taught you how to use NestJS .env environment variables with NestJS ConfigModule & dotenv. In summary, you learned the following:

  • How to create Nest.js env configuration module.
  • Using custom NestJS env configuration files for development and production.
  • Validating environment variables.
  • Using ConfigService in providers, modules, and in main.ts.
Guide to NestJS env variables with ConfigModule and dotenv

Written By:

Joseph Chege