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
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:
.envdevelopment (default).env.testtesting.env.productionproduction
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:
- Check file location: Ensure
.envis in the root directory of your project, not inside a subfolder. - Verify file name: Make sure the file is named exactly
.env(with a leading dot). - Confirm syntax: No spaces around
=, no comments, no trailing commas. - Check load order:
dotenv.config()must be called before any other modules that use environment variables. - Log loaded values: Add
console.log(process.env)after callingdotenv.config()to see what was actually loaded. - 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-fileorenvironment: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:
.envlocal development.env.localoverrides for local machine (ignored in git).env.testtesting environment.env.productionproduction 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
PORTto a number - Ensure
NODE_ENVis 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:
- envgen.com Generate .env files from templates
- dotenv.online Validate and convert .env files
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.