How to Use Dotenv in Nodejs

How to Use Dotenv in Node.js Managing configuration and sensitive data in Node.js applications has long been a challenge for developers. Hardcoding API keys, database credentials, and environment-specific settings directly into source code is not only insecure—it’s a violation of modern software development best practices. Enter dotenv : a zero-dependency module that loads environment variables fr

Nov 10, 2025 - 12:40
Nov 10, 2025 - 12:40
 1

How to Use Dotenv in Node.js

Managing configuration and sensitive data in Node.js applications has long been a challenge for developers. Hardcoding API keys, database credentials, and environment-specific settings directly into source code is not only insecureits a violation of modern software development best practices. Enter dotenv: a zero-dependency module that loads environment variables from a .env file into process.env, making configuration management clean, secure, and scalable. Whether youre building a small REST API or a large-scale microservice architecture, dotenv is an essential tool in the Node.js ecosystem. This comprehensive guide will walk you through everything you need to know to use dotenv effectivelyfrom installation and basic usage to advanced patterns, security best practices, and real-world examples.

Step-by-Step Guide

1. Understanding Environment Variables

Before diving into dotenv, its critical to understand what environment variables are and why they matter. Environment variables are dynamic named values that can affect the way running processes behave on a computer. In the context of Node.js applications, they are used to store configuration data such as:

  • Database connection strings
  • API keys and secrets
  • Port numbers
  • Server URLs
  • Feature flags

These values vary across environmentsdevelopment, staging, productionand should never be hardcoded into your application. Instead, they are injected at runtime, allowing the same codebase to operate securely in multiple contexts.

2. Installing Dotenv

To begin using dotenv in your Node.js project, you first need to install it via npm or yarn. Open your terminal, navigate to your project directory, and run:

npm install dotenv

Or if you prefer yarn:

yarn add dotenv

This installs the latest stable version of dotenv as a production dependency. Unlike development-only tools, dotenv is required at runtime, so it belongs in your dependencies, not devDependencies.

3. Creating a .env File

Once dotenv is installed, create a file named .env in the root directory of your project. This file will contain your environment variables in a simple key-value format:

DB_HOST=localhost

DB_PORT=5432

DB_NAME=myapp_dev

DB_USER=admin

DB_PASSWORD=secret123

API_KEY=abc123xyz

PORT=3000

NODE_ENV=development

Important notes:

  • Do not add spaces around the = sign.
  • Values can be wrapped in quotes if they contain special characters or spaces.
  • Comments are not supported in .env files. Avoid using

    for notes.
  • Never commit this file to version control.

4. Loading Environment Variables

To load the variables from your .env file into your Node.js application, you must require and configure dotenv at the very top of your main entry filetypically index.js, server.js, or app.js.

Heres how to do it:

require('dotenv').config();

console.log(process.env.DB_HOST); // Output: localhost

console.log(process.env.API_KEY); // Output: abc123xyz

Alternatively, if youre using ES6 modules (ESM), import it like this:

import dotenv from 'dotenv';

dotenv.config();

console.log(process.env.DB_HOST); // Output: localhost

Its crucial to load dotenv before any other modules that rely on environment variables. If you import a database connector or API client before calling dotenv.config(), those modules may attempt to read environment variables before theyre loaded, resulting in undefined values and runtime errors.

5. Accessing Variables in Your Application

Once dotenv has loaded your variables, you can access them anywhere in your application using process.env.VARIABLE_NAME. For example, heres a simple Express.js server that uses environment variables:

const express = require('express');

const app = express();

require('dotenv').config();

const PORT = process.env.PORT || 5000;

const DB_HOST = process.env.DB_HOST;

const API_KEY = process.env.API_KEY;

app.get('/', (req, res) => {

res.send('Server is running!');

});

app.listen(PORT, () => {

console.log(Server running on http://localhost:${PORT});

console.log(Database host: ${DB_HOST});

console.log(API Key loaded: ${API_KEY ? 'Yes' : 'No'});

});

In this example, the server reads the port from process.env.PORT, falling back to 5000 if not defined. It also logs the database host and verifies that the API key was successfully loaded.

6. Using Dotenv with Different Environments

Real-world applications require different configurations for development, testing, and production. Dotenv supports this through multiple .env files. Create separate files for each environment:

  • .env development (default)
  • .env.test testing
  • .env.production production

Each file contains environment-specific values:

.env

NODE_ENV=development

DB_HOST=localhost

DB_PORT=5432

API_KEY=dev_key_123

.env.production

NODE_ENV=production

DB_HOST=prod-db.example.com

DB_PORT=5432

API_KEY=prod_key_xyz

To load a specific file, pass the path option to config():

require('dotenv').config({ path: '.env.production' });

For more flexibility, you can dynamically load the correct file based on the NODE_ENV variable:

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

require('dotenv').config({ path: .env.${env} });

This approach ensures that your application automatically loads the correct configuration file based on the environment its running in.

7. Handling Missing or Invalid Variables

Its common for applications to depend on certain environment variables. If theyre missing, the app may crash or behave unpredictably. To prevent this, validate required variables at startup.

Create a simple validation function:

require('dotenv').config();

const requiredEnvVars = ['DB_HOST', 'DB_PORT', 'API_KEY', 'NODE_ENV'];

requiredEnvVars.forEach(varName => {

if (!process.env[varName]) {

throw new Error(Missing required environment variable: ${varName});

}

});

console.log('All required environment variables are set.');

Alternatively, use a library like joi or zod for more robust schema validation:

import { z } from 'zod';

const envSchema = z.object({

NODE_ENV: z.enum(['development', 'production', 'test']),

PORT: z.coerce.number().min(1000).max(65535),

DB_HOST: z.string().min(1),

DB_PORT: z.coerce.number(),

API_KEY: z.string().min(10),

});

const env = envSchema.parse(process.env);

console.log('Environment validated:', env);

Validating environment variables at startup prevents silent failures and makes debugging significantly easier.

8. Integrating with Popular Frameworks

Express.js

Express.js is one of the most popular Node.js frameworks. Integrating dotenv is straightforward:

const express = require('express');

const app = express();

require('dotenv').config();

app.get('/config', (req, res) => {

res.json({

env: process.env.NODE_ENV,

port: process.env.PORT,

dbHost: process.env.DB_HOST

});

});

app.listen(process.env.PORT || 3000, () => {

console.log(Express server running on port ${process.env.PORT || 3000});

});

Fastify

Fastify is a high-performance web framework. Load dotenv before initializing the server:

require('dotenv').config();

const fastify = require('fastify')({ logger: true });

fastify.get('/', async () => {

return { env: process.env.NODE_ENV, port: process.env.PORT };

});

fastify.listen({ port: process.env.PORT || 3000 }, (err, address) => {

if (err) {

console.error(err);

process.exit(1);

}

console.log(Server listening on ${address});

});

NestJS

NestJS has built-in support for environment variables via the @nestjs/config package, which uses dotenv under the hood. Install it:

npm install @nestjs/config

Then import it in your AppModule:

import { Module } from '@nestjs/common';

import { ConfigModule } from '@nestjs/config';

@Module({

imports: [

ConfigModule.forRoot({

isGlobal: true,

}),

],

})

export class AppModule {}

Now you can inject environment variables using the ConfigService:

import { Injectable } from '@nestjs/common';

import { ConfigService } from '@nestjs/config';

@Injectable()

export class AppService {

constructor(private configService: ConfigService) {}

getHello(): string {

return this.configService.get('API_KEY');

}

}

9. Debugging Dotenv Issues

If your environment variables arent loading as expected, follow these debugging steps:

  1. Check file location: Ensure .env is in the root directory of your project, not inside a subfolder.
  2. Verify file name: Make sure the file is named exactly .env (with a leading dot).
  3. Confirm syntax: No spaces around =, no comments, no trailing commas.
  4. Check load order: dotenv.config() must be called before any other modules that use environment variables.
  5. Log loaded values: Add console.log(process.env) after calling dotenv.config() to see what was actually loaded.
  6. Use absolute paths: If loading from a non-standard location, use path.join(__dirname, '.env') to avoid path resolution issues.

Example with absolute path:

const path = require('path');

require('dotenv').config({ path: path.join(__dirname, '..', '.env') });

Best Practices

1. Never Commit .env Files to Version Control

The most critical rule when using dotenv: never commit your .env file to Git or any other version control system. This file contains secrets that should remain private. Add .env to your .gitignore file:

.env

.env.local

.env.test

.env.production

Instead, provide a template file named .env.example that includes all required keys with placeholder values:

.env.example

DB_HOST=localhost

DB_PORT=5432

DB_NAME=myapp

DB_USER=

DB_PASSWORD=

API_KEY=

PORT=3000

NODE_ENV=development

This helps new developers understand what variables they need to set up without exposing real credentials.

2. Use .env Files Only for Development

In production, avoid using .env files entirely. Instead, inject environment variables through your deployment platform:

  • Heroku: Use Config Vars
  • Netlify: Use Environment Variables in Site Settings
  • Vercel: Use Environment Variables in Project Settings
  • Docker: Use --env-file or environment: in docker-compose.yml
  • Kubernetes: Use Secrets and ConfigMaps
  • CI/CD Pipelines: Use secrets in GitHub Actions, GitLab CI, etc.

Using environment variables directly from the system or platform is more secure than reading from a file, as files can be accidentally exposed via logs, backups, or misconfigured servers.

3. Use Different Files for Different Environments

As mentioned earlier, maintain separate .env files for each environment. This avoids accidental use of production credentials in development and simplifies configuration management.

Consider naming conventions:

  • .env local development
  • .env.local overrides for local machine (ignored in git)
  • .env.test testing environment
  • .env.production production environment

Use .env.local to override settings for your personal machine without affecting others on the team.

4. Validate and Sanitize Input

Environment variables are strings by default. Never assume theyre the correct type. Always validate and coerce them:

  • Convert PORT to a number
  • Ensure NODE_ENV is one of the allowed values
  • Check that API keys are not empty

Use libraries like zod, joi, or env-var to automate validation:

const env = require('env-var');

const port = env.get('PORT').required().asPortNumber();

const nodeEnv = env.get('NODE_ENV').required().asEnum(['development', 'production']);

const apiKey = env.get('API_KEY').required().asString();

console.log({ port, nodeEnv, apiKey });

5. Avoid Logging Sensitive Values

Never log environment variables that contain secrets. Even if you think youre only logging them in development, logs can be accessed by unauthorized users, stored indefinitely, or leaked via third-party monitoring tools.

Instead of:

console.log(API Key: ${process.env.API_KEY}); // ? DANGEROUS

Use:

console.log('API Key loaded successfully.'); // ? Safe

6. Use a .env Loader in Testing

When writing unit or integration tests, ensure your test environment has the correct variables loaded. Create a test/.env file and load it before running tests:

// test/setup.js

require('dotenv').config({ path: './test/.env' });

Then configure your test runner (e.g., Jest) to run this setup file:

// jest.config.js

module.exports = {

setupFilesAfterEnv: ['<rootDir>/test/setup.js'],

};

7. Rotate Secrets Regularly

Even if your .env file is secure, API keys and database passwords should be rotated periodically. Use secrets management tools like AWS Secrets Manager, HashiCorp Vault, or Azure Key Vault for production applications that require high security.

Tools and Resources

1. Dotenv CLI

While dotenv is primarily a Node.js library, theres also a command-line interface available for running scripts with environment variables:

npm install -g dotenv-cli

Now you can run Node.js scripts with a .env file loaded:

dotenv -e .env.production node server.js

This is especially useful for one-off scripts, migration tasks, or CLI tools that need environment variables.

2. dotenv-vault

For teams needing enhanced security, dotenv-vault provides encrypted .env files and collaboration features:

  • Encrypts secrets in .env files
  • Role-based access control
  • Team collaboration without exposing secrets
  • Seamless integration with existing dotenv workflows

Visit dotenv.org for more information.

3. env-cmd

env-cmd is another popular tool that allows you to run commands with environment variables from a config file:

npm install env-cmd

Configure it in package.json:

{

"scripts": {

"start": "env-cmd -f .env node server.js",

"start:prod": "env-cmd -f .env.production node server.js"

}

}

Useful for teams that prefer declarative configuration in package.json.

4. Config Libraries

For advanced applications, consider using dedicated configuration libraries:

  • config Uses JSON, YAML, or JS config files with environment overrides
  • node-config Supports hierarchical configuration, YAML, and environment-specific files
  • zod Type-safe schema validation for environment variables (recommended for TypeScript projects)

5. VS Code Extensions

Improve your workflow with these VS Code extensions:

  • .env Syntax highlighting and autocomplete for .env files
  • DotENV Provides IntelliSense and validation for .env files
  • Environment Variables Quickly view and edit environment variables

6. Online .env Generators

For generating sample .env files or converting between formats:

Real Examples

Example 1: Express.js App with MongoDB

Heres a complete example of a secure Express.js application using dotenv to connect to MongoDB:

.env

MONGO_URI=mongodb://localhost:27017/myapp

PORT=3000

NODE_ENV=development

JWT_SECRET=mysecretkey123

server.js

const express = require('express');

const mongoose = require('mongoose');

const dotenv = require('dotenv');

dotenv.config();

const app = express();

app.use(express.json());

// Validate required variables

const required = ['MONGO_URI', 'JWT_SECRET', 'PORT'];

required.forEach(key => {

if (!process.env[key]) {

throw new Error(Missing required environment variable: ${key});

}

});

// Connect to MongoDB

mongoose.connect(process.env.MONGO_URI)

.then(() => console.log('MongoDB connected'))

.catch(err => console.error('MongoDB connection error:', err));

// Simple route

app.get('/', (req, res) => {

res.json({

message: 'Hello from Node.js!',

env: process.env.NODE_ENV,

port: process.env.PORT

});

});

const PORT = process.env.PORT;

app.listen(PORT, () => {

console.log(Server running on port ${PORT});

});

Example 2: Node.js API with External Service

Connecting to a third-party API like Stripe or SendGrid:

.env

STRIPE_SECRET_KEY=sk_test_abc123xyz

SENDGRID_API_KEY=SG.xxxxx

EMAIL_FROM=noreply@myapp.com

NODE_ENV=production

emailService.js

const sgMail = require('@sendgrid/mail');

require('dotenv').config();

sgMail.setApiKey(process.env.SENDGRID_API_KEY);

const sendWelcomeEmail = async (email, name) => {

const msg = {

to: email,

from: process.env.EMAIL_FROM,

subject: 'Welcome to Our App!',

text: Hello ${name}, thanks for joining!, html: Hello ${name}, thanks for joining!,

};

try {

await sgMail.send(msg);

console.log('Welcome email sent successfully.');

} catch (error) {

console.error('Error sending email:', error);

}

};

module.exports = { sendWelcomeEmail };

Example 3: Dockerized Node.js App

When containerizing your app, avoid bundling .env files into the image. Instead, mount them at runtime:

Dockerfile

FROM node:18-alpine

WORKDIR /app

COPY package*.json ./

RUN npm ci --only=production

COPY . .

Do NOT copy .env into the image

ENV variables should be passed at runtime

EXPOSE 3000

CMD ["node", "server.js"]

docker-compose.yml

version: '3.8'

services:

app:

build: .

ports:

- "3000:3000"

env_file:

- .env

environment:

- NODE_ENV=production

depends_on:

- db

db:

image: postgres:15

environment:

POSTGRES_DB: myapp

POSTGRES_USER: user

POSTGRES_PASSWORD: pass

Run with:

docker-compose up

FAQs

Q1: Can I use dotenv in browser-based JavaScript?

No. Dotenv is designed for Node.js server-side applications. Browsers do not have access to the file system, and exposing .env files to the client would compromise security. Use environment variables injected at build time via tools like Vite, Webpack, or Create React Apps REACT_APP_ prefix instead.

Q2: Is dotenv secure enough for production?

Dotenv itself is secure for loading variables, but using .env files in production is discouraged. Production environments should use platform-native secrets management (e.g., AWS Secrets Manager, Kubernetes Secrets). Dotenv should be reserved for local development and CI/CD pipelines.

Q3: What happens if I forget to call dotenv.config()?

All environment variables will be undefined, even if they exist in your system. This often leads to cryptic errors like Cannot read property host of undefined when connecting to databases or APIs. Always load dotenv at the top of your entry file.

Q4: Can I use dotenv with TypeScript?

Yes. Install the types for better IntelliSense:

npm install --save-dev @types/dotenv

Then use it normally:

import dotenv from 'dotenv';

dotenv.config();

const port = process.env.PORT; // TypeScript now knows this is a string | undefined

For full type safety, combine with zod or env-var to validate and infer types.

Q5: How do I handle multiline values in .env files?

Dotenv supports multiline values using escaped newlines:

PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA...\n-----END RSA PRIVATE KEY-----"

Alternatively, use external files and read them with fs.readFileSync() in your code.

Q6: Why are my variables not loading in a nested folder?

By default, dotenv looks for .env in the current working directory (where you run node). If your entry file is in a subfolder like src/server.js, dotenv wont find .env unless you specify the path:

require('dotenv').config({ path: path.join(__dirname, '../.env') });

Q7: Can I override environment variables with dotenv?

Yes. Dotenv loads variables into process.env, and if a variable already exists, it will be overwritten. To prevent this, use the override: false option:

require('dotenv').config({ override: false });

This ensures system-level environment variables take precedence.

Q8: Whats the difference between dotenv and node-config?

dotenv loads key-value pairs from a .env file into process.env. Its simple and lightweight.

node-config uses hierarchical configuration files (JSON, YAML, JS) and supports environment-specific overrides, default values, and schema validation. Its more powerful but heavier. Use dotenv for basic needs; use node-config for complex apps.

Conclusion

Using dotenv in Node.js is one of the most fundamental and impactful practices for secure, maintainable, and scalable application development. By externalizing configuration into environment variables, you decouple your code from sensitive data, simplify deployment across environments, and adhere to the twelve-factor app methodology. This guide has walked you through everything from basic setup to advanced patterns, validation techniques, and real-world integrations.

Remember: never commit .env files, always validate your variables, prefer platform-managed secrets in production, and use templates like .env.example to onboard new developers safely. As your application grows, consider upgrading to more robust configuration systemsbut for most projects, dotenv remains the gold standard for simplicity and effectiveness.

By following the practices outlined here, youll not only avoid common security pitfalls but also build applications that are easier to test, deploy, and maintain. Dotenv isnt just a utilityits a cornerstone of modern Node.js development. Master it, and youll be better prepared to tackle the complexities of real-world applications with confidence and clarity.