How to Store API Keys
Securely in Node.js
The right way — from .env files in development to secrets managers in production.
How to store API keys in Node.js securely is one of those things that looks simple but has several ways to go wrong — hardcoding in source files, committing .env files, using production keys in development, or deploying keys in a way that leaks them into logs. This guide covers every layer from local dev to production.
The Wrong Way (and Why Developers Do It)
// Hardcoded — worst option
const stripe = new Stripe("sk_live_abc123xyz");
// In a config file committed to Git
module.exports = {
openaiKey: "sk-proj-abc123",
awsSecret: "wJalrXUtnFEMI"
};
// Passed as a CLI argument
node app.js --api-key=sk-live-abc
// Read from environment
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// .env file (never committed)
// STRIPE_SECRET_KEY=sk_live_abc123xyz
// Validated on startup
if (!process.env.STRIPE_SECRET_KEY) {
throw new Error("STRIPE_SECRET_KEY is required");
}
Step 1 — Install dotenv and Create Your .env File
npm install dotenv
# .env — never commit this file
STRIPE_SECRET_KEY=sk_live_your_key_here
OPENAI_API_KEY=sk-proj-your_key_here
DATABASE_URL=postgresql://user:password@localhost:5432/myapp
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
SENDGRID_API_KEY=SG.abc123
NODE_ENV=development
// At the very top of your entry file (index.js, app.js, server.js)
require('dotenv').config();
// Or with ES modules
import 'dotenv/config';
// Now access keys via process.env
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
Load dotenv before anything else
require('dotenv').config() must run before any code that reads process.env. If you import a module that reads env vars at import time, dotenv must be called first — or use the -r dotenv/config flag: node -r dotenv/config app.js.
Step 2 — .gitignore Is Non-Negotiable
Add these lines to your .gitignore before the first git add:
# .gitignore
.env
.env.*
.env.local
.env.*.local
!.env.example # Allow the example file to be committed
Commit a .env.example alongside it — this documents what variables are needed without exposing real values:
# .env.example — safe to commit, no real secrets
STRIPE_SECRET_KEY=sk_live_your_key_here
OPENAI_API_KEY=sk-proj-your_key_here
DATABASE_URL=postgresql://user:password@localhost:5432/myapp_dev
NODE_ENV=development
Step 3 — Validate on Startup
Fail fast if a required variable is missing rather than allowing a runtime error deep in your code when the key is first used:
// config.js — centralise all env var access
require('dotenv').config();
const required = [
'STRIPE_SECRET_KEY',
'OPENAI_API_KEY',
'DATABASE_URL',
];
for (const key of required) {
if (!process.env[key]) {
throw new Error(`Missing required environment variable: ${key}`);
}
}
module.exports = {
stripe: {
secretKey: process.env.STRIPE_SECRET_KEY,
},
openai: {
apiKey: process.env.OPENAI_API_KEY,
},
db: {
url: process.env.DATABASE_URL,
},
};
Import config.js at application startup. If any variable is missing, the process exits with a clear error message rather than a cryptic undefined error later.
Multiple Environments
Use separate .env files per environment and load the right one based on NODE_ENV:
# File structure
.env.development # local dev keys (test/sandbox keys only)
.env.test # keys for CI/CD test runs
.env.production # never stored in the repo — set on the server
# All three in .gitignore
.env.*
// Load the right file
require('dotenv').config({
path: `.env.${process.env.NODE_ENV || 'development'}`
});
Never use production keys in development
Create sandbox or test accounts for every API you use. Most providers (Stripe, OpenAI, Twilio, SendGrid) have separate test/sandbox environments with separate keys. If you develop with production keys, a bug in development can cause real charges, real emails to customers, or real data access.
Production — Don't Deploy a .env File
A .env file is a development convenience. In production, set environment variables directly on your platform — they're injected at runtime and never touch your codebase or Git history:
Team Secrets Management
When your team grows beyond one developer, sharing .env files via Slack or email is a security problem. Someone will paste a key in the wrong channel, or an old key will live in message history forever. A secrets manager solves this properly:
doppler run -- node app.js and never see or store the actual keys. Free for up to 5 users. Integrates with Vercel, Heroku, Railway.npm install -g @dopplerhq/cli
doppler setup
doppler run -- node app.js
npm install @infisical/sdk
import InfisicalClient from "@infisical/sdk";
const client = new InfisicalClient({ token });
const secret = await client.getSecret("STRIPE_KEY");
Never Log API Keys
The final leak vector: your own application logs. These are the most common ways keys end up in logs:
// ❌ Never log the full config object
console.log("App config:", config); // Logs all keys
console.log("Request:", req.headers); // May contain Authorization header
console.error("DB error:", connectionString); // May contain password in URL
// ✅ Log structured data without secrets
console.log("Stripe configured:", !!process.env.STRIPE_SECRET_KEY);
console.log("Request from:", req.ip, req.method, req.path);
// Never log req.headers or req.body in production
Before sharing any log output with a colleague, on StackOverflow or with an AI assistant, run it through a sanitizer to catch any keys that slipped through.
Strip Keys From Logs Before Sharing
Catches API keys, database URLs and tokens in logs. 100% client-side — nothing uploaded.
Open Log Sanitizer — Free →FAQ
How do I store API keys in Node.js? +
Use dotenv to load a .env file in development, and access keys via process.env.YOUR_KEY_NAME. Add .env to .gitignore immediately. In production, set variables directly on your hosting platform.
Should I commit my .env file to Git? +
Never. Add .env to .gitignore before your first commit. If you accidentally commit it, revoke the exposed keys immediately — see How to Remove API Keys From Git History for the cleanup steps.
What is the difference between process.env and dotenv? +
process.env is Node's built-in object for reading environment variables set by the OS or platform. dotenv reads a .env file and injects its values into process.env. In production you don't need dotenv — the platform sets the variables directly.
How do I share API keys with my team without emailing them? +
Use a secrets manager. Doppler (free for small teams) syncs secrets across all environments and team members without anyone ever seeing the raw key values. Infisical is the open-source self-hosted alternative. Both integrate directly with Node.js via their CLIs and SDKs.