Environment Variables and Secrets Management in Node.js: The Complete Guide
When you build applications with Node.js, you’ll often deal with sensitive information like:
- API keys
- Database connection strings
- Secret tokens
- Third-party service credentials
Hardcoding these secrets directly into your application code is dangerous.
Instead, the industry standard is to use environment variables and proper secrets management.
In this guide, you’ll learn:
- What environment variables are
- How to load and manage them in Node.js
- Best practices for secrets management
- Popular tools for secret storage
- How to handle secrets in different environments (dev, staging, production)
What Are Environment Variables?
Environment variables are key-value pairs stored outside of your source code.
They act like hidden settings that your application can access at runtime, without exposing sensitive information inside the codebase.
For example:
# Example .env file
DATABASE_URL=mongodb://username:password@db.example.com:27017/myapp
API_KEY=sk_test_abc123def456
NODE_ENV=production
Your Node.js app can read these environment variables using process.env
:
console.log(process.env.API_KEY);
Why Should You Use Environment Variables?
Security: Sensitive data stays outside your source code.
Flexibility: Easily change configuration across different environments (dev, test, prod).
Portability: Same codebase, different settings per environment.
Compliance: Many security standards (like GDPR, PCI DSS) require secrets to be properly managed.
Setting Up Environment Variables in Node.js
There are several ways to load environment variables in Node.js.
1. Manually Set Environment Variables
You can set environment variables when you start your Node app:
API_KEY=yourapikey node app.js
or on Windows:
set API_KEY=yourapikey && node app.js
2. Use a .env
File with dotenv
The easier and more common approach is using a .env
file and a library called dotenv
.
Install it:
npm install dotenv
At the very top of your entry file (e.g., app.js
):
require('dotenv').config();
Then create a .env
file in your project root:
# .env
PORT=3000
DATABASE_URL=mongodb://localhost:27017/myapp
JWT_SECRET=mySuperSecretKey
Now process.env.PORT
, process.env.DATABASE_URL
, and process.env.JWT_SECRET
are available inside your app.
Example: Secure MongoDB Connection
Here’s a quick example of connecting to MongoDB using environment variables:
require('dotenv').config();
const mongoose = require('mongoose');
mongoose.connect(process.env.DATABASE_URL)
.then(() => console.log('Connected to MongoDB!'))
.catch(err => console.error('MongoDB connection error:', err));
You don’t expose the password anywhere in your code.
The sensitive URL stays hidden inside .env
.
How to Organize and Manage Secrets
As your project grows, managing secrets becomes more complex.
You’ll need:
- Different values per environment (local, staging, production)
- Safe storage (no pushing
.env
files to GitHub) - Rotation (updating secrets without downtime)
Here’s how to do it properly:
1. Different .env
Files per Environment
You can create separate files:
.env
for local development.env.staging
for the staging server.env.production
for production
Load them conditionally:
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`
});
When starting your app:
NODE_ENV=production node app.js
This will load .env.production
automatically.
2. Never Commit Secrets to Git
Always ignore .env
files using .gitignore
:
# .gitignore
.env
.env.*
And use tools like git-secrets to scan commits for secret leaks.
3. Use a Secrets Manager in Production
When you move to production, you should avoid .env
files altogether.
Instead, use secrets management systems like:
- AWS Secrets Manager
- HashiCorp Vault
- Doppler
- Google Secret Manager
- Azure Key Vault
Example: Fetching secrets from AWS Secrets Manager inside a Lambda function.
const AWS = require('aws-sdk');
const client = new AWS.SecretsManager();
async function getSecrets() {
const secret = await client.getSecretValue({ SecretId: 'my/secret/id' }).promise();
return JSON.parse(secret.SecretString);
}
Then you can pull live secrets at runtime securely.
Advanced Secrets Management Techniques
When you build large-scale Node.js apps, you’ll want to go even deeper.
a. Auto-Reload Secrets Without Restarting App
Some secret managers support dynamic secret rotation.
You can listen for secret updates and reload config without downtime.
Example:
- Set up secret rotation in AWS Secrets Manager
- Reload config inside your Node.js app when version changes
b. Encrypt Secrets Locally
If you must store a .env
file locally, encrypt it:
- Encrypt the file using a tool like sops.
- Decrypt at runtime or during deployment.
This way even if your .env
file leaks, it’s still useless without the decryption key.
c. Use Environment Variables for Secrets, Config Files for Non-Sensitive Data
Follow the Twelve-Factor App recommendation:
- Keep secrets (passwords, API keys) in env vars.
- Keep non-sensitive config (e.g., feature toggles, labels) in versioned config files like
config.json
.
Separation of concerns makes systems easier to maintain.
Example Project: Secure API with Environment Variables
Let’s put it all together.
Imagine you are building a simple Express API.
.env
:
PORT=4000
JWT_SECRET=mysecret123
server.js
:
require('dotenv').config();
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/token', (req, res) => {
const token = jwt.sign({ user: 'admin' }, process.env.JWT_SECRET, { expiresIn: '1h' });
res.json({ token });
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Securely generate tokens without exposing the secret key anywhere in the codebase.
Best Practices Summary
Best Practice | Why It Matters |
---|---|
Never hardcode secrets in source code | Prevent accidental leaks |
Always use .gitignore for .env |
Avoid committing secrets |
Use a real secrets manager for production | More secure, scalable |
Separate secrets from app config | Cleaner architecture |
Rotate secrets regularly | Limit damage if leaks happen |
Limit permissions (Principle of Least Privilege) | Minimize attack surface |
Popular Tools and Libraries
Here’s a cheat sheet of tools you should know:
Tool | Purpose |
---|---|
dotenv |
Load .env files |
dotenv-safe |
Ensure required env vars exist |
vault CLI |
Interact with HashiCorp Vault |
aws-sdk |
Fetch secrets from AWS Secrets Manager |
dotenv-expand |
Expand nested env variables |
sops |
Encrypt/decrypt .env files |
Conclusion
Proper environment variables and secrets management is non-negotiable when building Node.js applications.
At first, using .env
files and dotenv
will be enough.
But as you scale into production environments, you’ll want to move to cloud-native secrets managers for maximum security.
Remember:
- Protect secrets like you protect passwords.
- Automate rotation wherever possible.
- Follow best practices from day one, so you don’t have painful migrations later.
With the right approach, you’ll build secure, scalable, and professional Node.js applications that handle secrets safely.
Leave a comment