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.

Reading an environment variable in Node.js
// 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.

.env
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=3000

Each line is VARIABLE_NAME=value. No quotes needed unless your value contains spaces. Convention is ALL_CAPS with underscores.

Loading .env with dotenv
// 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.

.env.example — commit this, fill in real values in .env.local
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=3000

Why 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:

.gitignore
# Environment files — never commit these
.env
.env.local
.env.*.local

# But do commit the example template
# .env.example is NOT in .gitignore

If 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.

Never do this

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:

Vite: only VITE_ prefixed vars go into the browser bundle
# 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_abc123
If you need to call a third-party API from your frontend, the safe pattern is: browser → your backend API → third-party API. Your server holds the secret key. Your frontend only ever talks to your own server.

Production 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
  • Herokuheroku config:set KEY=value
Next: see what counts as a secret and how API keys work — these pages go deeper on what to protect and why.