Environment variables

Reading · 6 min · Module 4, lesson 2 of 523 min left in this module

Module 4 · ConfigurationLesson 2 of 5

Goal: Read environment variables in code and set them in your shell and in a local .env file that never gets committed.

Key idea

An environment variable is a name and a text value that a program is given when it starts. Your code reads it by name; whatever started the program (your shell, a .env loader, a hosting platform) decides the value.

Reading them in code

Every language can read them. The value is always text, or missing.

// Node.js
const port = Number(process.env.PORT ?? 3000);
const apiKey = process.env.API_KEY;
# Python
import os

port = int(os.environ.get("PORT", "3000"))
api_key = os.environ["API_KEY"]  # raises KeyError if it's missing

Two habits help. Give optional settings a sensible default, like the port above. For required ones, such as a key, fail at start-up with a clear message instead of carrying on without it.

Because values are text, DEBUG=false is the string "false", which counts as true in both languages. Compare it to a string: process.env.DEBUG === "true".

Naming them

Names are case-sensitive on macOS and Linux, so api_key and API_KEY are different variables. The convention is upper case with underscores, and letters, digits and underscores only, not starting with a digit. Hosting platforms usually set a few for you, such as PORT, the port your app should listen on (lesson 1.4.3).

Pick one name per setting and use it everywhere: in your code, your .env file and the platform's settings.

Setting them in your shell

A variable set in a terminal applies to programs started from that terminal, until you close it.

export API_KEY="demo-not-a-real-key-0000"
node server.js

For one command only, put it in front: LOG_LEVEL=debug node server.js.

.env files, for your own machine

Typing every variable each time gets old. A .env file keeps them in one place for local development:

# .env: local development only, never committed
DATABASE_URL=postgres://app:localpass@localhost:5432/shop
API_KEY=demo-not-a-real-key-0000
LOG_LEVEL=debug

Your app doesn't read this file by itself. Something has to load it: node --env-file=.env server.js (Node 20.6 or later), the python-dotenv package, or your framework, which often does it for you.

Two rules keep it safe:

  1. Add .env to .gitignore before your first commit, so it never reaches the repository.
  2. Commit a .env.example instead, with every name and a placeholder value, so a teammate knows what to set.

On a hosting platform, there's no .env file. You set the same names in its settings, and it passes them to your app when it starts.

Where did PATH and HOME come from?

Your shell already has dozens of variables, such as PATH (where to look for programs) and HOME (your user folder). Run env on macOS or Linux, or Get-ChildItem env: in PowerShell, to list them. A program inherits a copy of its parent's variables. That's why export in one terminal doesn't reach another, and why a program can't change the variables of the shell that started it.

Check yourself

Your code runs if (process.env.DEBUG) { … } and DEBUG is set to false. Does the block run?
A teammate needs to know which variables to set to run the app locally. What do you commit?

In the docs