Environment variables and configuration across environments
Hardcoding a database URL or an API key directly into source code means the same value ships to every environment, and a real one leaks into version control forever — environment variables exist specifically to keep configuration OUT of the code that uses it.
3 min read
process.env: reading configuration from the environment, not the code
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.STRIPE_SECRET_KEY;
const port = process.env.PORT || 3000; // a real, common fallback pattern for local developmentprocess.env (introduced in this domain's first lesson as part of the process global) is a plain object containing every environment variable set for the currently-running process — set by the shell, a hosting platform's dashboard, a process manager, or a .env file loaded by a library. Reading configuration this way means the exact same code can run against completely different values (a local database vs. a production one, a test API key vs. a live one) purely by changing what's set in the environment, without touching a single line of source code.
Why hardcoded secrets are a real, common security incident, not just bad style
// NEVER do this — a real secret, committed to git, visible in the FULL HISTORY forever,
// even if a later commit "removes" it (git history still has the old commit)
const stripeKey = "sk_live_51H8x...";
// The fix — the actual value lives only in the environment, never in the repository
const stripeKey = process.env.STRIPE_SECRET_KEY;A hardcoded secret committed to a Git repository doesn't just risk exposure while it's the current code — it's permanently recoverable from the repository's history, even after a later commit deletes the line, unless the history itself is rewritten (a real, disruptive operation, not a routine fix). This is a genuinely common category of real security incident — a real credential accidentally committed, then scraped by automated bots that scan public repositories specifically for exposed keys — which is exactly why process.env (paired with never committing the actual .env file, covered next) is the standard practice, not a stylistic preference.
.env files for local development — and the .gitignore entry that has to go with them
# .env (a real file on disk, loaded by a library like dotenv, NEVER committed)
DATABASE_URL=postgres://localhost/myapp_dev
STRIPE_SECRET_KEY=sk_test_...
# .gitignore — MUST include this, or the whole point of .env is defeated
.env
A .env file lets a developer set local environment variables without manually exporting them in a shell every session — a library like dotenv reads the file and populates process.env at startup. This only works safely if .env is listed in .gitignore; forgetting that single line means the "local secrets file" gets committed anyway, which is exactly the hardcoded-secret problem this pattern exists to prevent, just moved into a differently-named file that developers might assume is automatically safe.
Validating configuration at startup, rather than discovering a missing value mid-request
const requiredEnvVars = ["DATABASE_URL", "STRIPE_SECRET_KEY", "SESSION_SECRET"];
for (const key of requiredEnvVars) {
if (!process.env[key]) {
console.error(`Missing required environment variable: ${key}`);
process.exit(1); // fail FAST, at startup — before the server even starts accepting requests
}
}Checking that every required environment variable is actually set before the server starts handling any requests turns a missing configuration value into an immediate, obvious startup failure — rather than a request crashing hours later, in production, the first time a code path that actually needs that specific variable finally runs. This "fail fast at startup" pattern is a real, deliberate practice: it's dramatically easier to diagnose "the server won't start, and it told me exactly which variable is missing" than "some request somewhere eventually threw an obscure error."
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Why is a hardcoded secret committed to Git a real, ongoing security risk even after a later commit removes it?
2. Why must `.env` be listed in `.gitignore`?
3. Why is validating required environment variables at startup (failing fast) better than discovering a missing one mid-request?