Skip to main content
Back to blog

Understanding environment variables

·3 min readWeb Dev

The first time I pushed an API key to GitHub, I got an email from the provider within minutes telling me the key had been revoked. That was my introduction to why environment variables matter.

What they are

Environment variables are key-value pairs that exist outside your code. Instead of hardcoding a database password in your source file, you store it as an environment variable and read it at runtime. Your code stays clean and your secrets stay out of version control.

# Set an environment variable
export DATABASE_URL="postgres://user:password@localhost:5432/myapp"
 
# Read it in Node.js
const dbUrl = process.env.DATABASE_URL;

The .env file

For local development, you store environment variables in a .env file at the root of your project:

DATABASE_URL=postgres://user:password@localhost:5432/myapp
API_KEY=sk-abc123
NODE_ENV=development

Libraries like dotenv (Node.js) load these automatically. Most frameworks (Next.js, Vite, Create React App) support .env files out of the box without extra dependencies.

The critical rule

Never commit .env files to git. Add .env to your .gitignore immediately when starting any project:

# .gitignore
.env
.env.local
.env.*.local

Instead, create a .env.example file that shows which variables are needed without the actual values:

DATABASE_URL=
API_KEY=
NODE_ENV=development

Commit .env.example so other developers (or future you) know what variables to set up.

Server-side vs client-side

This distinction trips people up, especially in frameworks like Next.js:

Server-side variables are available in your backend code and never sent to the browser. These are safe for secrets.

Client-side variables are bundled into your JavaScript and visible to anyone who opens the browser dev tools. Never put secrets here.

Next.js makes this explicit: variables prefixed with NEXT_PUBLIC_ are exposed to the client. Everything else stays server-side. Vite uses VITE_ as the prefix.

# Server only (safe for secrets)
DATABASE_URL=postgres://...
API_SECRET=sk-abc123

# Exposed to browser (never put secrets here)
NEXT_PUBLIC_API_URL=https://api.example.com

In production

In production, you set environment variables through your hosting platform's dashboard or CLI:

# Railway
railway variables set DATABASE_URL="postgres://..."
 
# Docker
docker run -e DATABASE_URL="postgres://..." myapp
 
# Docker Compose
services:
  app:
    environment:
      - DATABASE_URL=postgres://...

The values live in the deployment environment, not in your code. Different environments (development, staging, production) have different values for the same variable names.

Common mistakes

Checking if a variable exists. Always validate that required variables are set at startup:

const requiredVars = ["DATABASE_URL", "API_KEY"];
for (const v of requiredVars) {
  if (!process.env[v]) {
    throw new Error(`Missing required environment variable: ${v}`);
  }
}

This catches configuration errors immediately instead of failing mysteriously at runtime.

Using different variable names across environments. Pick a naming convention and stick with it. DATABASE_URL everywhere, not DB_URL in development and DATABASE_CONNECTION_STRING in production.

Sources

Enjoying the blog? Subscribe via RSS to get new posts in your reader.

Subscribe via RSS