Environment variables are variables that are set outside of a program. Using a .env file for keeping secrets (that we don’t want accidentally leaked out) is standard practice, but finding a strategy for managing .env files between multiple environments on a single machine isn’t so obvious.

When we talk about overcoming this challenge using NodeJS, we talk about using the dotenv package and one of the solutions is creating two or more .env files for each environment specifically. 

In this article, I will talk about three environments, so first we need to create three .env files: .env.development .env.testing , and .env.production file: 

(A quick aside: environment variables are, by convention, generally written in all caps.)

1.

NODE_ENV=development
DATABASE_URL=postgres://country:1234@localhost/country_dev

2.

NODE_ENV=testing
DATABASE_URL=postgres://country:1234@localhost/country_test

3.

NODE_ENV=production
DATABASE_URL=postgres://country:1234@localhost/country_prod

As it is mentioned before, we will use the dotenv package to load environment variables into the project, more precisely, the process.env interface. dotenv can be configured to read environment variables from files other than .env which is precisely what we need.

To use dotenv , first install it using the command: npm i dotenv

In this solution, we will have .env file that will be used as a controller and the place where we change the environment on which we want to run our tests and whose variables need to be loaded. For this purpose, I have set up a development environment:

NODE_ENV=development

After all .env files have been created, let’s create the main config file where we set up loading of specific environment variables and let the dotenv package know what file to load. We can name it env.config.ts

// import necessary packages
import dotenv from 'dotenv';
import path from 'path';

type Environment = "development" | "test" | "production"

class Env {

 private dotEnvDefault = ".env";
 private dotEnvTest = ".env.test";
 private dotEnvDevelopment = ".env.development";
 private dotEnvProduction = ".env.production";

 constructor() {
   this.init();
 }

 init() {

   // configure dotenv with the configuration in the .env file
   dotenv.config({
     path: path.resolve(process.cwd(), this.dotEnvDefault),
   });

   // and get the environment specified
   const environment = this.getEnvironment();

   // then load the name of the specific environment file
   const envFile = this.getEnvFile(environment);

   // and the last, re-configure dotenv with environment we send
   dotenv.config({
     path: path.resolve(process.cwd(), envFile),
   });
 }

 getEnvFile(environment: Environment): string {
   switch (environment) {
     case "development":
       return this.dotEnvDevelopment;
     case "test":
       return this.dotEnvTest;
     case "production":
       return this.dotEnvProduction;
   }
 }

 getEnvironmentVariable(variable: string): string {
   return process.env[variable];
 }

 getEnvironment(): Environment | null {
   return this.getEnvironmentVariable("NODE_ENV") as Environment;
 }

 // check which environment is set up
 isDevelopment() {
   return this.getEnvironment() === 'development';
 }

 isTest() {
   return this.getEnvironment() === 'test';
 }

 isProduction() {
   return this.getEnvironment() === 'production';
 }

}

const env = new Env();
export default env;

The last one is to create a file where all the above will be used. I will name it smoketest.tswhich contains a smoke test for an application we need to run in a specific environment. As we can see below, first of all, we need to make imports and initialize the environment; after that, I add variables to check which environment is set up, which could be used later in tests. 

import env from './env.config'
​
const environment = env.getEnvironment();
​
const isDevelopment = env.isDevelopment();
const isTest = env.isTest();
const isProduction = env.isProduction();

To sum up, with this solution, we can make as many .env  files as we need for testing purposes. There should be awareness that the dotenv package is loaded twice: first, to find out which environment we are currently in, then again to load the proper environment variable file. This is all handled in the env.config.ts file. By using this solution, it is always necessary to create a .env file that will point out where the variables we need are contained. In .env.development/testing/production files, we can add different environment variables that are specific for some environments, like: USER, DATABASE_URL, PORT, etc. 


“Load more .env files for different environments using NodeJS” Tech Bite was brought to you by Sanela Mešić, Test Engineer at Atlantbh.

Tech Bites are tips, tricks, snippets or explanations about various programming technologies and paradigms, which can help engineers with their everyday job.

Leave a Reply