Ask ChatGPT to "connect to my Stripe APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses." and it will hand you code with const STRIPE_KEY = 'sk_live_abc123' right in the source file. Ask Copilot the same thing and it autocompletes a hardcoded key before you even finish typing. The code works, but the moment you push it to GitHub, your secret is public. Bots scan every new commitWhat is commit?A permanent snapshot of your staged changes saved in Git's history, identified by a unique hash and accompanied by a message describing what changed. on public repos and can exploit a leaked key within minutes.
Environment variables are the industry-standard solution. They let you keep secrets out of your codebase entirely, and they let the same code run differently in development, staging, and production without changing a single line.
What an environment variableWhat is environment variable?A value stored outside your code that configures behavior per deployment, commonly used for secrets like API keys and database URLs. actually is
An environment variable is a named value that your operating system (or hosting platform) makes available to any process it runs. When Node.js starts, it reads those values into the process.env object, which you can query anywhere in your code.
// AI-generated code - hardcoded secret right in the file
const API_KEY = 'sk-1234567890abcdef'; // Visible in git history forever
// Correct approach - read from the environment
const API_KEY = process.env.API_KEY; // Value never appears in source codeThe most common things stored as environment variables are APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. keys, database connection strings, port numbers, log levels, and feature flags. Anything that differs between your machine and the server, or that you would regret leaking publicly, belongs here.
Why AI gets this wrong every time
AI code generators are trained on millions of tutorials and Stack Overflow answers. Tutorials almost always hardcode values for simplicity. The AI learns that pattern and reproduces it faithfully, it has no concept of "this value is secret" or "this will be pushed to a public repo."
process.env reads immediately.Here is what AI typically generates versus what you should actually write:
| AI generates | You should write | Why |
|---|---|---|
const db = 'postgres://user:pass@host/db' | const db = process.env.DATABASE_URL | Connection strings contain passwords |
const key = 'sk_live_abc123' | const key = process.env.STRIPE_KEY | API keys grant billing access |
const secret = 'my-jwt-secret' | const secret = process.env.JWT_SECRET | Signing keys can forge auth tokens |
fetch('http://localhost:3000/api') | fetch(process.env.API_URL) | URLs change between environments |
Accessing env vars in Node.js
process.env is available globally. You will often provide a fallback with || so your app does not crash if a non-critical variable is not set:
// Read with a sensible fallback for non-secret values
const port = process.env.PORT || 3000;
const logLevel = process.env.LOG_LEVEL || 'info';
// Branch on environment
if (process.env.NODE_ENV === 'production') {
console.log('Running in production mode');
}
// Everything in process.env is a string - parse explicitly
const isDebug = process.env.DEBUG === 'true'; // Not just === true
const maxRetries = Number(process.env.MAX_RETRIES) || 3;Common gotchas with process.env:
| Gotcha | Example | Fix |
|---|---|---|
| Values are always strings | process.env.PORT is '3000' not 3000 | Use Number() to convert |
| Undefined if not set | process.env.MISSING is undefined | Provide fallback with \|\| or validate at startup |
| Empty string is falsy | process.env.KEY = '' passes !process.env.KEY | Check === undefined if empty is valid |
| No live reload | Changing a variable requires restarting the process | Use nodemon or --watch in dev |
Accessing env vars in browser apps
Browser-based tools like Vite and Create React App only expose variables that carry a special prefix. This prevents you from accidentally leaking a server-side secret into a JavaScript bundleWhat is bundle?A single JavaScript file (or set of files) that a build tool creates by combining all your source code and its imports together. that ships to every user.
// Vite - variable must start with VITE_
const apiUrl = import.meta.env.VITE_API_URL;
// Create React App - variable must start with REACT_APP_
const apiUrl = process.env.REACT_APP_API_URL;
// Next.js - public vars start with NEXT_PUBLIC_
const apiUrl = process.env.NEXT_PUBLIC_API_URL;Setting env vars manually
Sometimes you need to set a variable quickly in your terminalWhat is terminal?A text-based interface where you type commands to interact with your computer. Also called the command line or shell. without a config file:
# macOS / Linux - lasts for the current terminal session
export PORT=4000
export API_KEY=sk_test_abc123
# Inline - set for a single command only
DATABASE_URL=postgres://localhost/mydb node server.js
# Windows PowerShell
$env:API_KEY="sk_test_abc123"For values you want every terminal sessionWhat is session?A server-side record that tracks a logged-in user. The browser holds only a session ID in a cookie, and the server looks up the full data on each request. to inherit, add export lines to your shell config (~/.zshrc, ~/.bashrc). Restart the terminal or run source ~/.zshrc for them to take effect.
Quick reference
| Where | How to access | Notes |
|---|---|---|
| Node.js | process.env.KEY | Always a string, available globally |
| Vite | import.meta.env.VITE_KEY | VITE_ prefix required, build-time only |
| Create React App | process.env.REACT_APP_KEY | REACT_APP_ prefix required |
| Next.js (client) | process.env.NEXT_PUBLIC_KEY | NEXT_PUBLIC_ prefix required |
| Set in shell | export KEY=value | Current session only |
| Set inline | KEY=value node app.js | Single command only |