Skip to main content

Command Palette

Search for a command to run...

🚀 Fixing dotenv in Node.js ES Modules: Why Environment Variables Show undefined and How to Solve It

Updated
3 min read

If you’ve ever worked with Node.js, dotenv, and ES Modules (import syntax), you may have faced this frustrating problem:

👉 Even after adding dotenv.config() at the very top of your code, your .env variables still show up as undefined in your config files.

I ran into this exact issue while setting up my database configuration. After some debugging, I discovered the root cause and found two simple solutions. Let’s break it down step by step.

🔍 The Problem

My project structure looked like this:

project/
├── config/
│   └── db.config.js
├── index.js
└── .env

In my index.js, I had:

import dotenv from "dotenv";
dotenv.config();

import dbConfig from "./config/db.config.js";

And in my db.config.js:

export default {
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  password: process.env.DB_PASS,
};

I expected process.env.DB_HOST and others to load from my .env file.
But instead, I kept getting:

undefined

😤 Super frustrating.

🕵️ Root Cause: Import Hoisting in ES Modules

The reason this happens comes down to how ES Modules work in Node.js.

  • In ESM, import statements are hoisted. This means they run before any other code in the file.

  • So even though I wrote dotenv.config() at the top of index.js, my db.config.js was imported before dotenv had a chance to load the .env file.

  • That’s why my database config was getting undefined.

👉 In CommonJS (require), this isn’t a problem, because requires happen in order, not hoisted.

Solutions

I found two reliable solutions for this issue, depending on your setup.

  1. Use Node.js --env-file (Best & Recommended):

    If you’re on Node.js v20 or later, you can use the built-in --env-file flag. This loads your .env file before anything else runs.

    Run your app like this:

     node --env-file=.env index.js
    

    Or update your package.json:

     "scripts": {
       "start": "node --env-file=.env index.js"
     }
    

    This way, process.env already has your environment variables before imports happen.
    ✅ Clean, ✅ Simple, ✅ Works great for development and testing.

  2. Create a Separate dotenv Loader File:

    If you don’t want to rely on --env-file or you’re using an older Node version, create a small dotenv loader file.

    For example, config/env.js:

     import dotenv from "dotenv";
     dotenv.config();
    

    Then, in your index.js:

     import "./config/env.js"; // Load dotenv first
     import dbConfig from "./config/db.config.js";
    

    This ensures dotenv runs before your database config is evaluated.
    It’s a simple and portable fix.

📘 Notes & Learnings

  • ES Modules execute imports before code, which is why dotenv doesn’t work the way you expect.

  • Use --env-file for a clean solution in Node.js 20+.

  • Alternatively, create a dedicated dotenv loader file if you want portability.

  • Always check execution order when your environment variables don’t load properly.

🎯 Conclusion

If your .env variables are coming back as undefined in Node.js ES Modules, don’t panic it’s usually because of import hoisting.

✅ The easiest fix:
Use Node’s --env-file flag:

node --env-file=.env index.js

✅ The alternate fix:
Create a dotenv loader file and import it first in your entry file.

Both methods work well for development and testing environments.

1️⃣0️⃣…Crafting code, one bit at a time…0️⃣1️⃣