Understanding environment variables
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
Related posts
Why I built Omnibase: a universal database MCP server
I got tired of copy-pasting query results between DataGrip and AI agents. So I built an MCP server that gives AI agents secure, direct access to any database.
Delta libraries: how diffing works and which library to use
What delta libraries do, how diff algorithms work under the hood, and a practical comparison of the most popular options in the JavaScript ecosystem.
Offline-first apps: harder than it sounds
Building apps that work without internet is one of those things that seems straightforward until you actually try it. Here is what makes it hard and how to approach it.
Enjoying the blog? Subscribe via RSS to get new posts in your reader.
Subscribe via RSS