Environment variables & .env files
Every real project has secrets — API keys, database passwords, tokens. Environment variables are how you keep them out of your code and out of your Git history. This is one of the most important concepts for any developer to get right.
What is an environment variable?
An environment variable is a named value stored outside your code, in the environment where your program runs. Your operating system has them, your terminal has them, and your Node.js app can read them too.
Think of your code as a recipe and environment variables as the ingredients you swap out depending on where you're cooking. The same code runs differently in development (your laptop) and production (the live server) because the environment variables are different.
// process.env gives you all environment variables as an object
const apiKey = process.env.OPENAI_API_KEY;
const dbUrl = process.env.DATABASE_URL;
const port = process.env.PORT || 3000;Notice the || 3000 fallback on PORT. If the environment variable isn't set, use 3000 as the default. This pattern is common in development.
The .env file
A .env file is a simple text file that sets environment variables for your project. You put it in the root of your project, and a library called dotenv loads it automatically when your app starts.
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
OPENAI_API_KEY=sk-proj-abc123def456
STRIPE_SECRET_KEY=sk_live_xyz789
JWT_SECRET=some-long-random-string-here
PORT=3000Each line is VARIABLE_NAME=value. No quotes needed unless your value contains spaces. Convention is ALL_CAPS with underscores.
// At the very top of your entry file (index.js / server.js)
import 'dotenv/config';
// Now process.env has all your .env values
console.log(process.env.PORT); // "3000"Install dotenv with npm install dotenv. Load it once at the very top of your entry file — before any other imports that might use environment variables.
The four .env file variants
Most frameworks (Vite, Next.js, Create React App) have a convention for multiple .env files that apply in different situations:
.env
The base file. Loaded in all environments. Usually contains non-secret defaults — safe public values, port numbers, feature flags. Keep secrets out of this one if you commit it.
.env.local
Your personal overrides. Never committed to Git. This is where your actual API keys and secrets go during development. Overrides .env.
.env.production
Values that apply only in production. Committed to Git only if it contains no secrets — sometimes used for public production URLs or feature flags. In practice, most teams set production secrets directly in the hosting platform (Railway, Vercel, etc.) instead.
.env.example
A template showing all the variable names your project needs, with fake or empty values. This one IS committed to Git so new team members know what variables they need to set up. Never put real values here.
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
OPENAI_API_KEY=sk-proj-your-key-here
STRIPE_SECRET_KEY=sk_live_your-key-here
JWT_SECRET=replace-with-a-long-random-string
PORT=3000Why Git ignores .env files
Your .gitignore file tells Git which files to never track. A properly configured project always includes .env and .env.local in .gitignore:
# Environment files — never commit these
.env
.env.local
.env.*.local
# But do commit the example template
# .env.example is NOT in .gitignoreIf a .env file containing real secrets is committed to a GitHub repository — even a private one — those secrets are now in your Git history. Even if you delete the file later, it remains in every commit where it existed. Attackers regularly scan GitHub for exposed secrets.
Do not commit a .env file containing real API keys, passwords, or tokens. Even to a private repository. Even "just temporarily." Even if you plan to delete it later.
Frontend vs backend — what goes where
This is critical. Environment variables split into two categories: those your server can use, and those your frontend JavaScript bundle exposes to the world.
✓ Backend only (server)
Database credentials, API secret keys, JWT secrets, Stripe secret key, OpenAI key, session secrets. These run in Node.js on the server — users never see them.
✗ Never in frontend JS
Any secret key. If it lives in browser JavaScript, anyone can open DevTools → Sources and read it. There are no exceptions to this rule.
Frameworks like Vite and Next.js use a naming convention to control this:
# This goes into your browser JS bundle — anyone can read it
VITE_PUBLIC_API_URL=https://api.yoursite.com
# This stays server-side only — NEVER in browser
STRIPE_SECRET_KEY=sk_live_abc123Production secrets — not in files
On a production server, you usually don't use a .env file at all. Instead, you set environment variables directly in the hosting platform. This is cleaner and more secure — the values never exist as files on disk that could be accidentally exposed.
- Railway — Variables tab in your service settings
- Vercel — Project settings → Environment Variables
- Cloudflare Workers — Secrets via wrangler secret put SECRET_NAME
- Heroku — heroku config:set KEY=value