How to load different .env files with dotenv the correct way

Learn how to work with multiple .env files with the help of dotenv so you can test your app with different environment variables values.

How to load different .env files with dotenv the correct way

Introduction

An Environment file (.env file) is a sustainable way to store environment variables while in development. With the help of the dotenv package, we can easily load those environment variables into our app which need them. But sometimes we want to have more than just one .env file to test different values of environment variables, let's say a .env.development while developing and a .env.test while testing.
In this article, I'm about to show you how you can dynamically load the relevant .env file depending on the current node environment variable (NODE_ENV) value.

Create a custom dotenv script

First, create a script dotenv.js under a scripts folder, and write the following code:

// dotenv.js
const { config } = require("dotenv");
const { getEnvFile } = require("./utils/dotenvUtils");

const env = process.env.NODE_ENV || "development";

/**
 * The dotenv module will override any variables passed to the command,
 * if it is also defined in th .env file.
 */
config({ path:  getEnvFile(env) });

You can notice that the getEnvFile() function is not defined yet. It will be responsible for looking up the correct .env file to load in the repository. It expects the environment value ("development" or, "test" or "production") as an argument and returns the corresponding .env file.

Get the relevant .env file

To implement the getEnvFile() function, create a file scripts/utils/dotenvUtils.js then write the following code:

// dotenvUtils.js
const { join } = require("path");
const { existsSync, lstatSync } = require("fs");

const allowedEnvironments = ["development", "production", "test"];

/**
 * Given an environment variable, gets the path to the corresponding .env file.
 *
 * @param {"development" | "production" | "test"} env - The current environment variable.
 * @returns {string} The absolute path to the environment variable file.
 */
function getEnvFile(env) {
  if (!allowedEnvironments.includes(env)) {
    throw new Error(`unknown environment: '${env}'`);
  }

  /**
   * The earlier in this array, the higher priority is.
   */
  const envFilesByPriority = [
    `.env.${env}.local`,
    `.env.${env}`,
    ".env.local",
    ".env",
  ];

  let envPath;
  for (const envFile of envFilesByPriority) {
    const fullPath = path.join(process.cwd(), envFile);
    if (!fs.existsSync(fullPath) || !lstatSync(fullPath).isFile()) continue;
    envPath = fullPath;
    break;
  }

  if (!envPath) throw new Error("no valid .env file found");
  return envPath;
}

exports.getEnvFile = getEnvFile;

That code does explain itself pretty much well, but to give more insights here is a brief explanation:

  • Only "development" or, "production" or, "test" are accepted values as environment variables, otherwise, an error is thrown.

  • I kinda like how Vite.js does to prioritize which .env file is going to be loaded in a Vitejs project. So to replicate that behavior, that's why I created the envFilesByPriority array.

  • Then we loop through the envFilesByPriority array and if the current item does exist as a file inside the directory, we get its full path and return its value from our function. Otherwise, if none of them match the conditions, an error is thrown.

Use the custom dotenv script

From there we can use our custom dotenv.js script in our project:

// src/index.js
require("../scripts/dotenv")

// the rest of your code goes here

Now to make it work in the desired environment you need to supply the NODE_ENV variable whenever running your app with node:

NODE_ENV=test node src/index.js

If NODE_ENV is omitted then it will default to "development" as you can see in the first code block of this article.

Custom dotenv go!!

We have seen how to work with multiple and different .env files and dynamically load the relevant .env file in the function of the current NODE_ENV variable in a Node.js project by creating a custom dotenv script. The dotenv package is the core of our custom dotenv script. Now we can test our project with different values of environment variables easily.