process.env is an object with one property per environment variable that the shell passed to your Node.js program: process.env.PORT, process.env.DATABASE_URL, and so on. A variable that was not set reads as undefined, and a variable that was set is always a string. Read it once at startup, give it a default, and convert it to the type you need.
It printed shop on port 8080 in eu-west (REGION was never set, so its default took over), then string number: the variable is text until Number() converts it. Delete the first two assignments and run again to see every default.
Set environment variables in the shell
The variables come from wherever Node.js is started. In a terminal:
// macOS and Linux: for one command
PORT=8080 NODE_ENV=production node app.js
// macOS and Linux: for the rest of the terminal session
export PORT=8080
// Windows PowerShell
$env:PORT = "8080"; node app.js
// Windows cmd.exe
set PORT=8080
node app.js
Hosting platforms (Heroku, Render, Vercel, Docker, Kubernetes) have a settings page or file for the same thing; your code reads them the same way.
Everything is a string
This is where most process.env bugs come from. 'false' and '0' are non-empty strings, so they are truthy:
The naive check printed debug is ON for a variable set to false, so compare against the exact string you expect. For numbers, Number(process.env.X) turns a missing value into NaN, so apply the default before converting, as the first example did with ??.
NODE_ENV
NODE_ENV is a convention, not a Node.js feature: Node.js never sets it. Libraries read it. Express, for one, hides stack traces from error pages when it is production, and many tools skip development-only checks.
It printed mode: production and verbose logs: false; change 'production' to 'development' and the second line flips. Stick to production, development and test, because libraries check for those exact words.
.env files: --env-file and dotenv
Typing every variable before node gets old. A .env file keeps them in your project folder, one per line:
// .env
PORT=8080
DATABASE_URL=postgres://localhost/shop
API_KEY=dev-key-123
Node.js 20.6 and later read it with a flag, no package needed:
node --env-file=.env app.js
On older versions, or if you prefer it, install the dotenv package (npm install dotenv) and load it at the very top of your entry file, before any code reads process.env:
require('dotenv').config(); // copies .env into process.env
console.log(process.env.PORT); // 8080
Neither one overwrites a variable that is already set in the shell, so production settings win over the file.
Fail early on missing variables
A missing database URL should stop the program at startup with a clear message, not crash on the first request an hour later:
It printed Missing environment variables: API_KEY; add process.env.API_KEY = 'test'; at the top and it prints Config OK. In a real program, follow the message with process.exit(1), because a nonzero exit code is how a deploy script or a container platform sees that startup failed.
Never commit secrets
Put .env in .gitignore the day you create it. Commit a .env.example with the variable names and fake values instead, so others know what to set. A key that was ever pushed to a repository should be treated as leaked and replaced, even if you delete the commit later.
Frequently Asked Questions
Why is process.env.MY_VAR undefined?
The variable was not set in the shell that started Node.js, or it was set in a different terminal. A .env file is not read automatically either: start Node.js with --env-file=.env or load it with dotenv before you read the value.
Can I change process.env while the program runs?
Yes. Assigning process.env.X = 'value' changes it for the current process and for any child process it starts later, but never for the shell that launched Node.js. Node.js converts the value you assign to a string.
Are environment variable names case-sensitive?
On Linux and macOS, yes: PORT and port are two variables. On Windows they are not. Stick to uppercase names with underscores and the question never comes up.
Can the browser read process.env?
No. Browsers have no environment variables. Build tools such as Vite and Next.js copy selected variables into your frontend code at build time, which also means those values are public.