Course Lessons

JAVASCRIPT FOUNDATIONS + AI MINDSET

Back to Course

Environment Variables and Security

JAVASCRIPT... Lesson 20 of 41 8 min

Environment Variables and Security

Protect Your Secrets · .env · process.env

By AI Learning Assistant  ·  Node.js Security  ·  Environment Variables  ·  Best Practices

🤖 AI-POWERED LESSON

This article is paired with a live AI session. You will learn to protect your API keys and sensitive data using environment variables — a critical skill for any production application. Every code block includes detailed line-by-line comments.

Every day, thousands of developers accidentally expose their API keys on GitHub. Bots scan repositories constantly, looking for keys. Within minutes of a public commit, those keys are compromised and used by attackers. The cost can range from a few dollars in API charges to complete account takeover.

The solution is simple but crucial: environment variables. Instead of hardcoding secrets in your source code, you store them in a special file (`.env`) that never gets committed to version control. Your code reads these values at runtime from the environment. This keeps secrets out of your codebase while still making them available to your application.

We will cover what environment variables are, using dotenv to load .env files, accessing variables with process.env, creating .gitignore to protect secrets, and the top 5 security mistakes beginners make with API keys. By the end, you will know how to keep your secrets safe.

What Are Environment Variables?

KEY-VALUE PAIRS · SEPARATE FROM CODE

Environment variables are key-value pairs stored outside your application code. They are set in the operating system's environment and accessed by your application at runtime. In Node.js, you access them through the process.env object.

The dotenv package loads variables from a `.env` file into process.env automatically. This allows you to have different configurations for development, staging, and production without changing your code.

📜 CODE BLOCK 1: Complete Environment Setup (with line-by-line comments)

// ============================================
// STEP 1: INSTALL DOTENV
// ============================================
// $ npm install dotenv

// ============================================
// STEP 2: CREATE .env FILE
// ============================================
// Create a file named .env in your project root with content:
//
// OPENAI_API_KEY=sk-abc123yourkeyhere
// PORT=3000
// APP_NAME=Webbo3
// DATABASE_URL=postgresql://localhost:5432/mydb
// NODE_ENV=development

// ============================================
// STEP 3: CREATE .gitignore FILE
// ============================================
// Create .gitignore in your project root with content:
//
// .env
// node_modules/
// .env.local
// .env.production
// *.log
// .DS_Store

// ============================================
// STEP 4: LOAD AND USE ENVIRONMENT VARIABLES
// ============================================

// WHY: Load environment variables from .env file into process.env
// HOW: This must be called BEFORE using any environment variables
require("dotenv").config();

// WHY: Import the OpenAI SDK
const OpenAI = require("openai");

// WHY: Initialize client using API key from environment variable
// Notice: No hardcoded key! The key stays in .env, not in source code
const client = new OpenAI({ 
  apiKey: process.env.OPENAI_API_KEY
});

// WHY: Read other configuration values from environment
// This makes the app configurable without code changes
const PORT = process.env.PORT || 3000;  // Fallback default if not set
const APP_NAME = process.env.APP_NAME || "MyApp";
const NODE_ENV = process.env.NODE_ENV || "development";

// WHY: Check if API key is available (but don't log the actual key!)
// !! converts any truthy value to true, falsy to false
console.log("App:", APP_NAME);
console.log("Port:", PORT);
console.log("Environment:", NODE_ENV);
console.log("AI is ready:", !!process.env.OPENAI_API_KEY);

// ============================================
// SECURITY CHECK: Add this to validate required vars
// ============================================

if (!process.env.OPENAI_API_KEY) {
  console.error("FATAL ERROR: OPENAI_API_KEY is not set in environment variables");
  console.error("Create a .env file with OPENAI_API_KEY=your_key_here");
  process.exit(1);  // Exit with error code, don't start the app
}
    

Why Hardcoding is Dangerous

SECURITY BREACH · ACCOUNT TAKEOVER · FINANCIAL LOSS

🚫 THE DANGER OF HARDCODED SECRETS

What happens when you hardcode an API key? The key becomes part of your source code. If you commit that code to GitHub (public or private), anyone with access can see it. But worse — automated bots constantly scan GitHub commits for patterns that look like API keys. When they find one, they immediately start using it.

Real consequences: Attackers can make API calls on your account (costing you money), access your users' data, delete your resources, or use your infrastructure for their own purposes. Some developers have received bills for $50,000+ from compromised cloud accounts.

The fix is simple: Use environment variables and never commit your .env file.

LIVE AI LEARNING SESSION

Ask your AI assistant this question ↓

💬 YOU ASK THE AI:

"What are the top 5 security mistakes beginner Node.js developers make when handling API keys and sensitive data? How do I avoid each one?"

🤖 AI RESPONDS (Top 5 Security Mistakes):

1. Hardcoding API keys directly in source code
const apiKey = "sk-abc123"
const apiKey = process.env.API_KEY
Why it's dangerous: Anyone who sees your code (on GitHub, in a shared screen, or on a leaked laptop) gets your key forever. Fix: Always use environment variables. Never commit keys to version control.

2. Committing .env file to GitHub
git add .env
echo ".env" >> .gitignore
Why it's dangerous: The .env file is meant to be local only. Once committed, your secrets are in the git history permanently — even if you delete it later, it's still in the commit history. Fix: Always add .env to .gitignore BEFORE creating the file.

3. Logging sensitive data to console
console.log("API Key:", apiKey)
console.log("API Key set:", !!apiKey)
Why it's dangerous: Logs can be read by anyone with server access, and in production, logs are often aggregated to monitoring services. Fix: Never log secrets directly. Log a boolean indicating presence instead.

4. Using the same API key for development and production
Same key in .env and .env.production
Different keys with different permissions and rate limits
Why it's dangerous: If a developer's dev key leaks, it has production access. If you need to revoke the key, both dev and prod break. Fix: Use separate keys with least-privilege permissions for each environment.

5. Storing secrets in client-side JavaScript (browser code)
fetch('https://api.com?key=' + apiKey) in frontend code
Make API calls from your backend server only
Why it's dangerous: Any API key in frontend JavaScript can be extracted by anyone using browser dev tools. There is NO way to hide keys in client-side code. Fix: Never put API keys in frontend code. Always proxy API calls through your own backend.

📝 MY NOTE — IN MY OWN WORDS

This was a wake-up call. I used to hardcode API keys in my projects because it was "faster" for testing. I never committed them to GitHub, but I didn't realize that just having them in source code on my laptop was a risk. The AI's point about logging secrets was something I never considered — I've definitely logged keys while debugging. Now I always use environment variables, even for local development. The .gitignore checklist is now part of every project I start. The separate keys for dev/prod makes so much sense — I can be aggressive with dev key rotation without breaking production. I feel much more confident about deploying secure applications now.

Essential .gitignore Reference

WHAT TO EXCLUDE FROM VERSION CONTROL

# ============================================
# RECOMMENDED .gitignore FOR NODE.JS PROJECTS
# ============================================

# Environment variables (CRITICAL — contains secrets!)
.env
.env.local
.env.production
.env.staging
.env.*.local

# Dependencies (can be regenerated with npm install)
node_modules/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db

# Editor files
.vscode/
.idea/
*.swp
*.swo
*~

# Build outputs
dist/
build/
out/

# Coverage directories
coverage/
.nyc_output/
    

Production Environment Variables

PLATFORM-SPECIFIC · NEVER COMMIT

In production, you don't use a .env file. Instead, each hosting platform provides a way to set environment variables through their dashboard or CLI:

🌐 Platform-Specific Methods:

  • Heroku: heroku config:set OPENAI_API_KEY=value
  • Vercel: vercel env add OPENAI_API_KEY or Dashboard → Settings → Environment Variables
  • Netlify: Site settings → Build & deploy → Environment → Environment variables
  • AWS Elastic Beanstalk: Environment properties in configuration
  • DigitalOcean App Platform: App spec with env variables or Dashboard → Settings → Environment Variables
  • Render: Dashboard → Environment → Environment Variables

The .env file is for local development only. In production, set variables through your platform's secure interface.

📜 CODE BLOCK 3: Environment Variable Validation Pattern

// ============================================
// PROFESSIONAL VALIDATION PATTERN
// ============================================

// Load dotenv first
require("dotenv").config();

// Define required environment variables
const requiredEnvVars = [
  "OPENAI_API_KEY",
  "DATABASE_URL"
];

// Check each required variable
const missingVars = requiredEnvVars.filter(varName => !process.env[varName]);

// If any are missing, exit with clear error message
if (missingVars.length > 0) {
  console.error("❌ Missing required environment variables:");
  missingVars.forEach(v => console.error(`   - ${v}`));
  console.error("\n💡 Create a .env file with these variables or set them in your environment.");
  process.exit(1);
}

// Helper to get environment variable with type conversion
function env(key, defaultValue = null) {
  const value = process.env[key];
  if (value === undefined && defaultValue === null) {
    throw new Error(`Environment variable ${key} is required but not set`);
  }
  return value !== undefined ? value : defaultValue;
}

// Usage:
const PORT = env("PORT", "3000");
const API_KEY = env("OPENAI_API_KEY");  // Will throw error if missing
    

Quick Comparison

Practice❌ Insecure✅ Secure
API Key StorageHardcoded in source codeprocess.env with .env file
Version Control.env committed to git.env in .gitignore
DebuggingLogging full secretsLogging presence only (!!key)
Environment SeparationSame key for dev/prodDifferent keys with least privilege
Frontend KeysAPI keys in client-side JSBackend-only proxy pattern

🤖 HOW AI ACCELERATES THIS TOPIC

Ask AI: "Scan this code snippet and tell me if there are any hardcoded secrets or security issues." Get an instant security audit.

Ask: "I accidentally committed my .env file with real API keys. What do I do now?" Get step-by-step incident response steps.

Ask: "Generate a .gitignore file for a Node.js project with all security-sensitive files excluded." Get production-ready configuration.

AI-Assisted JavaScript Learning · Environment Variables · Keep Your Secrets Safe

Complete this lesson

Mark as complete to track your progress