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.
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.