Working with Environment Variables
Learn how to use .env files to manage secrets, configure different environments, and keep sensitive data out of your codebase.
Why environment variables exist
Your application needs secrets — API keys, database passwords, service credentials — and configuration that changes between environments. Hardcoding these values is dangerous and inflexible. If you push a file containing your Stripe secret key to GitHub, bots will find it within minutes and use it to make fraudulent charges. If you hardcode your database URL as localhost, the app breaks when you deploy it. Environment variables solve both problems. They store configuration outside your code, in the environment where the code runs. On your laptop, the DATABASE_URL variable points to localhost. On your production server, it points to your real database. Same code, different configuration. The operating system provides environment variables to every running process. Run: printenv in your terminal to see all the variables currently set. You will see PATH (where the system looks for commands), HOME (your home directory), and possibly hundreds more. Your application can read these using process.env in Node.js. Run: node -e "console.log(process.env.HOME)" to see it in action. Ask Claude Code: Show me all the environment variables my current shell session has and explain the most important ones. Understanding that environment variables are a system-level feature — not a Node.js invention — helps you use them correctly across any language or platform.
Creating and using .env files
Typing export DATABASE_URL=postgres://localhost:5432/mydb every time you open a terminal is tedious and error-prone. The .env file convention solves this. Create a file called .env in your project root. Add your variables, one per line: DATABASE_URL=postgres://localhost:5432/mydb, API_KEY=sk_test_abc123, PORT=3000, NODE_ENV=development. No quotes needed for simple values. Use quotes for values with spaces: APP_NAME="My Cool App". No spaces around the equals sign. Install the dotenv package: npm install dotenv. At the top of your entry file (src/index.ts or similar), add: import 'dotenv/config'. This reads the .env file and loads its variables into process.env. Now process.env.DATABASE_URL returns your database URL. Ask Claude Code: Set up dotenv in my project and create a .env file with placeholder values for the configuration this project needs. Claude Code will read your codebase, find everywhere you use process.env, and generate a .env file with all the required variables. Critical rule: never commit .env files to git. Add .env to your .gitignore file immediately. Instead, create a .env.example file that lists all required variables with placeholder values: DATABASE_URL=postgres://localhost:5432/your_database, API_KEY=your_api_key_here. Commit .env.example so other developers know which variables they need to set. Common error: if process.env.API_KEY is undefined, check three things — is dotenv imported before you access the variable, is the .env file in the project root (not a subdirectory), and is the variable name spelled exactly the same (case-sensitive).
Validating environment variables at startup
The worst time to discover a missing environment variable is when a user triggers the code path that needs it — perhaps at 2 AM during a production incident. Validate all required variables when your application starts. Ask Claude Code: Create an environment validation module at src/config.ts that checks all required variables exist on startup and exits with a helpful error if any are missing. A good validation module does three things. First, it checks that every required variable is defined and non-empty. If DATABASE_URL is missing, the app should fail immediately with a message like: Missing required environment variable: DATABASE_URL. See .env.example for required variables. Second, it validates formats where possible. A PORT should be a number between 1 and 65535. A URL should start with http:// or https://. An email should contain an @ symbol. Third, it exports typed configuration so the rest of your code uses config.databaseUrl (a validated string) instead of process.env.DATABASE_URL (which TypeScript types as string | undefined). Run: npx ts-node src/index.ts without a .env file. You should see clear error messages listing every missing variable. Fix them one at a time or create the .env file from .env.example. Ask Claude Code: Add validation for each environment variable in my project. Check types, formats, and required fields. Output a clear error message listing all problems, not just the first one. This fail-fast approach saves hours of debugging. Instead of cryptic errors deep in your code, you get a clear checklist at startup. Common error: process.env values are always strings. If you expect a number, parse it: const port = parseInt(process.env.PORT || '3000', 10). If you expect a boolean, check the string: const debug = process.env.DEBUG === 'true'.
Managing multiple environments
Real projects have multiple environments — development (your laptop), staging (a test server), and production (the live server). Each needs different configuration. Create three files: .env.development, .env.staging, and .env.production. Development uses localhost databases, test API keys, and debug logging. Staging uses a staging database, test API keys, and info logging. Production uses the production database, live API keys, and error-only logging. Load the right file based on the NODE_ENV variable. Ask Claude Code: Update my config module to load environment-specific .env files. If NODE_ENV is production, load .env.production. If staging, load .env.staging. Default to .env.development. The dotenv package supports this with the path option: require('dotenv').config({ path: '.env.' + process.env.NODE_ENV }). Update your npm scripts to set the environment: set dev to NODE_ENV=development node src/index.js, set start to NODE_ENV=production node src/index.js. On Windows, use the cross-env package: cross-env NODE_ENV=production node src/index.js. Some hosting platforms (Vercel, Railway, Heroku) let you set environment variables through their dashboard. This is the preferred approach for production — no .env.production file needed, and secrets never touch your filesystem. Ask Claude Code: Show me how to configure environment variables on Vercel and Railway for this project. Commit only .env.example and .env.development to git. Staging and production .env files should either be on the server only or set through the hosting dashboard. Never commit production secrets to any repository, even a private one — people leave companies, repositories get cloned, and secrets in git history persist even after deletion.
Secrets management and security best practices
Environment variables are the minimum viable approach to secrets management. For serious projects, you need additional safeguards. First, rotate secrets regularly. If you use an API key for six months and it leaks, six months of requests are compromised. Rotating monthly limits exposure. Ask Claude Code: Create a script that checks the age of my API keys and reminds me to rotate any older than 90 days. Second, use different secrets for each environment. Your development Stripe key should be a test key (sk_test_...), not the production key (sk_live_...). If your development database gets compromised, production data stays safe. Third, never log secrets. A common mistake is logging the entire config object for debugging, which prints every secret to your log files. Ask Claude Code: Scan my codebase for any console.log or logging statements that might accidentally expose environment variables. Create a sanitised logging helper that redacts sensitive values. Fourth, use a secrets manager for production. Tools like AWS Secrets Manager, HashiCorp Vault, or Doppler store secrets encrypted and provide them to your application at runtime. They also handle rotation, access control, and audit logging. For small projects, hosting platform environment variables are sufficient. For anything handling financial data or personal information, invest in a proper secrets manager. Ask Claude Code: Set up a secrets management approach appropriate for my project's scale. If it is a small project, use hosting platform variables. If it handles sensitive data, recommend a secrets manager. Common error: git push with a .env file. If this happens, the secret is in git history forever. Immediately rotate all exposed secrets, then use git filter-branch or BFG Repo Cleaner to remove the file from history.
Environment variables in frameworks and deployment
Modern frameworks have their own conventions for environment variables. Next.js uses a NEXT_PUBLIC_ prefix for variables that should be available in the browser. Without this prefix, the variable is server-only — it cannot be accessed in client-side code. This is a security feature: you do not want your database password sent to the browser. Variables like NEXT_PUBLIC_API_URL are safe to expose because they point to public endpoints. Variables like DATABASE_URL must remain server-only. Ask Claude Code: Review my Next.js project and ensure no server-only secrets are exposed to the client via the NEXT_PUBLIC_ prefix. Vite uses VITE_ as its prefix. Create React App uses REACT_APP_. Each framework documents its convention — follow it strictly. For deployment, each platform has its own way to set variables. Vercel: go to Project Settings, then Environment Variables. You can set different values for Production, Preview, and Development. Railway: go to your service, then Variables. Variables can reference other variables and be shared across services. Docker: use the -e flag (docker run -e DATABASE_URL=... myapp) or an env_file directive in docker-compose.yml. Ask Claude Code: Create a deployment checklist for my project that lists every environment variable needed, where to set it on the hosting platform, and whether it is a secret or a public value. Run through this checklist before every deployment. A missing environment variable in production causes downtime. A leaked secret causes a security incident. Both are preventable with discipline and tooling.
Security Fundamentals
This guide is hands-on and practical. The full curriculum covers the conceptual foundations in depth with structured lessons and quizzes.
Go to lesson