How to Use Dotenv in Nodejs
How to Use Dotenv in Node.js Managing configuration settings in Node.js applications can quickly become a chaotic and error-prone task—especially as projects grow in complexity. Hardcoding sensitive data like API keys, database credentials, or environment-specific URLs directly into your source code is a serious security risk and violates modern development best practices. This is where dotenv com
How to Use Dotenv in Node.js
Managing configuration settings in Node.js applications can quickly become a chaotic and error-prone taskespecially as projects grow in complexity. Hardcoding sensitive data like API keys, database credentials, or environment-specific URLs directly into your source code is a serious security risk and violates modern development best practices. This is where dotenv comes in as an essential tool for Node.js developers.
Dotenv is a zero-dependency module that loads environment variables from a .env file into process.env. It simplifies configuration management by allowing developers to store sensitive and environment-specific data in a separate, ignored filekeeping codebases clean, secure, and portable across different environments like development, staging, and production.
In this comprehensive guide, youll learn exactly how to use dotenv in Node.jsfrom installation and basic usage to advanced patterns, real-world examples, and industry best practices. Whether youre building a REST API, a full-stack application, or a microservice, mastering dotenv will improve your applications security, maintainability, and scalability.
Step-by-Step Guide
Step 1: Install Dotenv
The first step to using dotenv is installing it via npm or yarn. Open your terminal, navigate to your Node.js project directory, and run one of the following commands:
npm install dotenv
or
yarn add dotenv
This installs the dotenv package as a production dependency since its required for your application to run in any environment. Unlike development-only tools (like nodemon), dotenv is essential for proper configuration loadingeven in production, as long as environment variables are properly managed.
Step 2: Create a .env File
Next, create a file named .env in the root directory of your project. This file will contain key-value pairs representing your environment variables. Each line should follow the format:
KEY=VALUE
For example, heres what a typical .env file might look like:
PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp_dev
DB_USER=admin
DB_PASSWORD=supersecretpassword123
JWT_SECRET=myjwtsecretkeythatshouldbe32characterslong
API_KEY=abc123xyz987
BASE_URL=https://api.example.com
Important notes:
- Do not use spaces around the
=sign. - Values can be wrapped in quotes if they contain spaces or special characters:
API_KEY="abc 123 xyz" - Comments are not supported in .env files. Avoid using
or // for annotations.
- Always use uppercase for variable namesits a widely accepted convention.
Step 3: Load Environment Variables in Your App
To load the variables from your .env file, you must require and configure dotenv at the very top of your main application filetypically server.js, app.js, or index.js.
Heres how:
require('dotenv').config();
console.log(process.env.PORT); // Output: 3000
console.log(process.env.DB_HOST); // Output: localhost
Its critical that require('dotenv').config(); is called before any other code that relies on environment variables. If you import a configuration module or database connection file before loading dotenv, those files will read undefined values from process.env, leading to runtime errors.
Alternatively, if youre using ES6 modules (i.e., "type": "module" in package.json), use the import syntax:
import dotenv from 'dotenv';
dotenv.config();
console.log(process.env.PORT);
Step 4: Use Environment Variables in Your Application
Once dotenv has loaded the variables, you can access them anywhere in your application using process.env.VARIABLE_NAME.
Heres a practical example using Express.js:
const express = require('express');
const app = express();
require('dotenv').config();
const PORT = process.env.PORT || 5000;
const DB_HOST = process.env.DB_HOST;
const DB_PASSWORD = process.env.DB_PASSWORD;
app.get('/', (req, res) => {
res.send('Server is running!');
});
app.listen(PORT, () => {
console.log(Server running on http://localhost:${PORT});
});
In this example, the server will listen on port 3000 (as defined in .env). If PORT is not defined in .env, it defaults to 5000. This fallback mechanism is useful for development but should be avoided for critical variables like database credentials.
Step 5: Configure Your Database Connection
One of the most common use cases for dotenv is managing database connection strings. Heres an example using the PostgreSQL client pg:
const { Pool } = require('pg');
require('dotenv').config();
const pool = new Pool({
user: process.env.DB_USER,
host: process.env.DB_HOST,
database: process.env.DB_NAME,
password: process.env.DB_PASSWORD,
port: process.env.DB_PORT,
});
pool.query('SELECT NOW()', (err, res) => {
console.log(err, res);
pool.end();
});
By externalizing credentials, you avoid exposing them in version control and make it easy to switch between different databases for different environments.
Step 6: Use Dotenv in Multiple Environments
Real-world applications often require different configurations for development, testing, and production. Dotenv supports this through multiple .env files.
Create separate files:
.env.development.env.production.env.test
Each file contains environment-specific values:
.env.development
PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_PASSWORD=devpass123
.env.production
PORT=8080
NODE_ENV=production
DB_HOST=prod-db.example.com
DB_PASSWORD=prodsecretpassword!
To load a specific environment file, pass the path option to config():
require('dotenv').config({ path: .env.${process.env.NODE_ENV} });
Now, when you start your application with:
NODE_ENV=production node server.js
Dotenv will automatically load .env.production. If the file doesnt exist, it falls back to .env.
Step 7: Integrate with Package.json Scripts
For convenience, define scripts in your package.json to set environment variables before running your app:
{
"scripts": {
"start": "node server.js",
"dev": "NODE_ENV=development node server.js",
"prod": "NODE_ENV=production node server.js",
"test": "NODE_ENV=test jest"
}
}
On Windows, the above syntax may not work. Use the cross-env package for cross-platform compatibility:
npm install cross-env --save-dev
Then update your scripts:
{
"scripts": {
"dev": "cross-env NODE_ENV=development node server.js",
"prod": "cross-env NODE_ENV=production node server.js",
"test": "cross-env NODE_ENV=test jest"
}
}
Now you can run npm run dev or npm run prod safely on any operating system.
Step 8: Validate Environment Variables
Its good practice to validate that required environment variables are present before starting your application. Otherwise, your app may crash unexpectedly in production.
Create a simple validation utility:
require('dotenv').config();
const requiredEnvVars = ['DB_HOST', 'DB_USER', 'DB_PASSWORD', 'JWT_SECRET'];
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);
if (missingVars.length > 0) {
console.error('Missing required environment variables:', missingVars);
process.exit(1);
}
console.log('All required environment variables are set.');
You can place this validation at the top of your main server file. This ensures your application fails fast with a clear error message if critical variables are missing.
Best Practices
Never Commit .env to Version Control
Your .env file contains sensitive information and should never be tracked by Git or any other version control system. Add it to your .gitignore file:
.env
.env.local
.env.*.local
This prevents accidental exposure of credentials if your repository becomes public. Always provide a template file.env.examplethat lists all required variables with placeholder values:
.env.example
PORT=3000
NODE_ENV=development
DB_HOST=localhost
DB_PORT=5432
DB_NAME=your_database
DB_USER=your_username
DB_PASSWORD=your_password
JWT_SECRET=your_jwt_secret_here
API_KEY=your_api_key_here
Other developers can copy .env.example to .env and fill in their own values. This keeps the project setup clear and consistent.
Use Different Files for Different Environments
As mentioned earlier, avoid using a single .env file for all environments. Use .env.development, .env.production, etc., to isolate configuration. This reduces the risk of accidentally using development credentials in production.
Do Not Store Secrets in Code or Logs
Even with dotenv, never log environment variables or include them in error messages. For example, avoid this:
console.log('Connecting to DB:', process.env.DB_PASSWORD); // ? Dangerous!
Instead, log only the connection status:
console.log('Connecting to database at', process.env.DB_HOST); // ? Safe
Use Environment Variables for All Configuration
Dont just use dotenv for passwords. Use it for everything that changes between environments: API endpoints, cache timeouts, feature flags, logging levels, and third-party service URLs. This makes your application truly portable.
Keep Variables Organized and Documented
As your project grows, your .env file can become unwieldy. Group related variables logically and document their purpose in .env.example:
Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=myapp
DB_USER=admin
DB_PASSWORD=secret
Authentication
JWT_SECRET=your_32_char_secret_here
JWT_EXPIRES_IN=24h
External APIs
STRIPE_SECRET_KEY=sk_test_...
TWILIO_ACCOUNT_SID=AC...
TWILIO_AUTH_TOKEN=your_auth_token
This improves onboarding and reduces confusion among team members.
Use a Configuration Module for Complex Apps
For large applications, consider creating a dedicated configuration module to encapsulate dotenv logic and provide type safety (if using TypeScript) or validation:
// config/index.js
require('dotenv').config();
const config = {
port: process.env.PORT || 3000,
env: process.env.NODE_ENV || 'development',
db: {
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT, 10) || 5432,
name: process.env.DB_NAME,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
},
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: process.env.JWT_EXPIRES_IN || '1d',
},
};
module.exports = config;
Then import it in your app:
const config = require('./config');
app.listen(config.port, () => {
console.log(Server running on port ${config.port});
});
This approach keeps your code DRY, testable, and scalable.
Use Docker and Environment Variables Together
If youre deploying your Node.js app with Docker, you can pass environment variables at runtime using the -e flag or a .env file with docker-compose:
docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .env.production
environment:
- NODE_ENV=production
This ensures your containerized app uses the correct configuration without hardcoding values into the image.
Rotate Secrets Regularly
Even with dotenv, secrets stored in files are vulnerable if the file system is compromised. Implement a policy to rotate API keys, database passwords, and JWT secrets regularlyespecially after team member changes or security incidents.
Tools and Resources
Dotenv-Parser
For advanced parsing needs (like handling arrays or nested objects), consider dotenv-parse or dotenv-expand. These tools extend dotenvs functionality:
- dotenv-expand: Allows variable expansion within .env files. Example:
DB_URL=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST} - dotenv-safe: Validates required variables and provides default values. Useful for stricter environments.
Install dotenv-expand:
npm install dotenv-expand
Usage:
require('dotenv').config();
require('dotenv-expand')(process.env);
VS Code Extensions
Enhance your .env editing experience with these extensions:
- DotENV Syntax highlighting and autocomplete for .env files
- ENV Color-codes keys and values, provides quick editing
Online .env Validators
Before committing a .env.example file, validate its syntax using online tools like:
These tools check for invalid characters, missing equals signs, or malformed entries that could cause silent failures.
Security Scanning Tools
Use tools like TruffleHog or GitGuardian to scan your repositories for accidentally committed secretseven if you think youve ignored .env. These tools detect patterns matching API keys, tokens, and passwords in commits.
Environment Variable Managers
For teams managing many projects, consider centralized secret management tools:
- Vault by HashiCorp Enterprise-grade secrets management
- AWS Secrets Manager For apps hosted on AWS
- 1Password or Bitwarden For storing and sharing .env templates securely
These are especially valuable for production deployments where environment variables should not be stored in files at all.
Documentation Resources
- Official Dotenv GitHub Repository
- The Twelve-Factor App: Config Foundational principles behind environment-based configuration
- Node.js Process Environment Documentation
Real Examples
Example 1: Express.js API with MongoDB
Lets build a minimal Express API that connects to MongoDB using dotenv.
.env
MONGO_URI=mongodb://localhost:27017/myapi
PORT=5000
NODE_ENV=development
JWT_SECRET=your_jwt_secret_here
server.js
const express = require('express');
const mongoose = require('mongoose');
require('dotenv').config();
const app = express();
app.use(express.json());
// 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('/api/users', (req, res) => {
res.json({ message: 'Hello from API!' });
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => {
console.log(Server running on port ${PORT});
});
package.json
{
"name": "express-mongo-api",
"version": "1.0.0",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.6.0",
"dotenv": "^16.4.5"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}
This setup allows developers to run the app locally without modifying code. Production deployments can override MONGO_URI via environment variables in the hosting platform.
Example 2: Node.js CLI Tool with API Keys
Imagine youre building a CLI tool that interacts with a third-party service like GitHub:
.env
GITHUB_TOKEN=ghp_abc123xyz789
API_BASE_URL=https://api.github.com
cli.js
!/usr/bin/env node
const { request } = require('http');
const require('dotenv').config();
const token = process.env.GITHUB_TOKEN;
const baseUrl = process.env.API_BASE_URL;
if (!token) {
console.error('Error: GITHUB_TOKEN is not set in .env file.');
process.exit(1);
}
const options = {
hostname: 'api.github.com',
path: '/user',
headers: {
'Authorization': Bearer ${token},
'User-Agent': 'my-cli-tool'
}
};
const req = request(options, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
console.log(JSON.parse(data).login);
});
});
req.on('error', err => {
console.error('Request failed:', err.message);
});
req.end();
Run with:
node cli.js
This ensures the API key is never hardcoded into the script and can be changed per user without touching the source code.
Example 3: Testing with Jest and Environment Variables
When writing unit tests, you often need to mock environment variables. Dotenv makes this easy:
.env.test
NODE_ENV=test
DB_HOST=localhost
DB_NAME=test_db
test/database.test.js
const { Pool } = require('pg');
require('dotenv').config({ path: '.env.test' });
describe('Database Connection', () => {
test('should connect to test database', async () => {
const pool = new Pool({
host: process.env.DB_HOST,
database: process.env.DB_NAME,
});
const res = await pool.query('SELECT 1');
expect(res.rows[0].?column?).toBe(1);
});
});
Run tests with:
npm run test
Since the test environment uses a dedicated .env file, you avoid polluting your development database during testing.
FAQs
What happens if I dont use dotenv?
If you dont use dotenv, youll likely hardcode configuration values into your source code. This exposes secrets in version control, makes deployments inconsistent across environments, and increases the risk of accidental leaks. It also makes collaboration harderevery developer must manually edit config files, leading to merge conflicts and errors.
Can I use dotenv in production?
Yesbut with caution. While dotenv works in production, its generally recommended to set environment variables directly in your hosting platform (e.g., Heroku, Vercel, AWS, Docker) rather than using a .env file. This avoids storing secrets on the filesystem. Dotenv can still be used locally to simulate production variables during testing.
Is dotenv secure?
Dotenv itself is secureit simply reads a file and assigns values to process.env. However, security depends on how you manage the .env file. Never commit it to Git. Restrict file permissions. Use encrypted secret managers for production. Dotenv is a tool; security is your responsibility.
Can I use dotenv with TypeScript?
Yes. Install the type definitions:
npm install --save-dev @types/dotenv
Then use it the same way:
import dotenv from 'dotenv';
dotenv.config();
console.log(process.env.PORT);
TypeScript will now recognize process.env properties. For better type safety, define an interface:
interface Env {
PORT: string;
NODE_ENV: string;
DB_HOST: string;
}
declare global {
namespace NodeJS {
interface ProcessEnv extends Env {}
}
}
Why does my .env file not load?
Common causes:
- Missing
require('dotenv').config();at the top of your file. - File is named incorrectly (e.g.,
envinstead of.env). - File is in the wrong directoryit must be in the root of your project.
- Youre using ES6 modules but forgot to use
importsyntax. - Another module (like a config loader) runs before dotenv and tries to access
process.envtoo early.
Do I need to restart my server after changing .env?
Yes. Environment variables are loaded only once when the application starts. Changes to .env files require a server restart to take effect. Tools like nodemon can auto-restart the server on file changes, making development smoother.
Can I use dotenv with React or other frontend frameworks?
Nodotenv is a Node.js module and does not work in browsers. Frontend frameworks like React use different methods (e.g., Vites VITE_ prefix or Create React Apps REACT_APP_ prefix) to expose environment variables. Never use dotenv in frontend code to store secretsthose would be exposed to users.
How do I handle arrays or complex objects in .env?
Dotenv doesnt natively support arrays or nested objects. Store them as comma-separated strings:
ALLOWED_ORIGINS=http://localhost:3000,https://example.com
Then parse in code:
const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');
For complex objects, use JSON strings:
API_CONFIG={"baseUrl":"https://api.example.com","timeout":5000}
Parse with:
const apiConfig = JSON.parse(process.env.API_CONFIG);
Conclusion
Dotenv is a simple yet indispensable tool for any Node.js developer serious about clean, secure, and maintainable code. By externalizing configuration into environment variables, you decouple your application from its deployment environment, reduce security risks, and improve collaboration across teams.
This guide has walked you through the full lifecycle of using dotenvfrom installation and basic usage to advanced patterns like multi-environment files, validation, and integration with testing and deployment pipelines. Youve seen real-world examples in Express.js, CLI tools, and testing frameworks, and learned best practices that align with industry standards like the Twelve-Factor App methodology.
Remember: dotenv is not a magic solution. Its a foundation. The real power comes from how you use it. Never commit .env files. Validate critical variables. Use separate files for each environment. Combine it with secure secret management in production. And always document your configuration clearly.
As your Node.js applications grow in complexity, mastering dotenv will save you countless hours of debugging, reduce deployment failures, and make your codebase more professional and trustworthy. Start using it todayand never hardcode another password again.