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:[email protected]/country_dev
2.
NODE_ENV=testing DATABASE_URL=postgres://country:[email protected]/country_test
3.
NODE_ENV=production DATABASE_URL=postgres://country:[email protected]/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 a .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.ts
, which 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.