DEV Community

Binary Journal
Binary Journal

Posted on

Environment Variables Done Right (and Safe)

The Problem with Hardcoding

We've all been there: you need an API key, a database URL, or a secret token. The quickest fix is to paste it right into the code. It works, but it's a ticking time bomb. Commit that file, push to a public repo, and your secret is exposed. Even in private repos, every developer with access now has the key, and rotating it becomes a nightmare.

Hardcoded config also makes your app brittle. You can't run different settings in dev, staging, and production without editing code. That's why environment variables exist.

What Are Environment Variables?

Environment variables are key-value pairs set outside your application, in the operating system or runtime environment. Your code reads them at runtime. This keeps secrets out of the source tree and lets you change config without touching code.

In Node.js, you access them via process.env. In Python, os.environ. In Go, os.Getenv. The pattern is universal.

The Basic Pattern

// Node.js example
const apiKey = process.env.API_KEY;
if (!apiKey) {
  throw new Error('API_KEY is required');
}
Enter fullscreen mode Exit fullscreen mode

That's the minimum. But you can do better.

Using a .env File for Local Development

For local dev, you don't want to export variables manually every time. The .env file is the standard solution. It's a plain text file with KEY=VALUE lines. Tools like dotenv (Node), python-dotenv (Python), or godotenv (Go) load it into your process.

# .env (never commit this!)
DB_URL=postgres://localhost:5432/mydb
API_KEY=supersecret
Enter fullscreen mode Exit fullscreen mode
# Python with python-dotenv
from dotenv import load_dotenv
load_dotenv()

import os
db_url = os.getenv('DB_URL')
Enter fullscreen mode Exit fullscreen mode

The Golden Rule: Never Commit .env

Add .env to your .gitignore immediately. Instead, commit a .env.example with placeholder values and comments explaining each variable. This gives new developers a template without exposing secrets.

# .env.example (commit this)
# Database connection string
DB_URL=postgres://user:pass@localhost/db
# API key for external service
API_KEY=replace_me
Enter fullscreen mode Exit fullscreen mode

Loading Config Safely in Production

In production, you usually set environment variables via your hosting platform (Heroku, AWS, Docker, etc.). Your code should just read them. But you need to handle missing variables gracefully.

function getRequired(name) {
  const value = process.env[name];
  if (!value) {
    throw new Error(`Missing required environment variable: ${name}`);
  }
  return value;
}

const dbUrl = getRequired('DB_URL');
Enter fullscreen mode Exit fullscreen mode

This fails fast instead of crashing later with a cryptic error.

Type Validation and Defaults

Environment variables are strings. If you need a number or boolean, parse and validate.

const port = parseInt(process.env.PORT, 10) || 3000;
const isDev = process.env.NODE_ENV !== 'production';
Enter fullscreen mode Exit fullscreen mode

For more complex config, consider a config module that centralizes all reads and exports a typed object.

// config.ts
export const config = {
  port: parseInt(process.env.PORT, 10) || 3000,
  dbUrl: process.env.DB_URL ?? '',
  isProd: process.env.NODE_ENV === 'production',
};
Enter fullscreen mode Exit fullscreen mode

Secrets Management in Production

For serious applications, environment variables alone aren't enough for highly sensitive secrets. Use a dedicated secrets manager like AWS Secrets Manager, HashiCorp Vault, or cloud-specific services. These integrate with your app at runtime and provide rotation, audit logs, and access control.

But for most projects, environment variables with strict guardrails are perfectly fine. The key is to never store secrets in code, never log them, and never commit them.

Common Pitfalls

  • Accidental commits: Use tools like git-secrets or pre-commit hooks to scan for potential secrets.
  • Leaking in logs: Don't log the entire config object. Be explicit about what you log.
  • Spaces and quotes: In .env files, quotes are part of the value unless your loader strips them. Use KEY=value without quotes.
  • Line endings: Use LF, not CRLF, to avoid parsing issues on Windows.

A Minimal Safe Setup

  1. Create .env.example and commit it.
  2. Create .env locally and gitignore it.
  3. Use a loader like dotenv in development only.
  4. In production, rely on the platform's native env vars.
  5. Validate required variables at startup and fail fast.
  6. Never log secrets.

That's it. It's not glamorous, but it's solid. Environment variables are the baseline for secure and flexible configuration. Master them, and you'll avoid a whole class of embarrassing leaks and configuration bugs.

Final Thought

Security is a habit, not a feature. Treat environment variables as the first line of defense. Keep your secrets out of the repo, your config explicit, and your startup checks strict. Your future self, and your users, will thank you.

Top comments (0)