mosaddik.dev
All posts

Environment variables in Node.js: .env files without leaking secrets

Mosaddik Billah 3 min read

JavaScript & React

A red padlock resting on a computer keyboard
Photo by FlyD on Unsplash
On this page
  1. Reading an environment variable
  2. .env files, without a package
  3. Never commit .env
  4. Check them at startup
  5. In production
  6. Frontend code can’t keep secrets

API keys, database passwords and other settings that change between your laptop and the live server shouldn’t be written in your code. They belong in environment variables: values the operating system or hosting platform gives your program when it starts. This guide shows how to use them in Node.js, including the built-in support for .env files, and the mistakes that leak secrets.

Reading an environment variable#

In Node.js, every environment variable is in process.env:

JavaScript
const port = process.env.PORT || 3000;
const apiKey = process.env.PAYMENT_API_KEY;

Values are always strings. process.env.DEBUG set to false is the text "false", which counts as true in an if. Convert explicitly:

JavaScript
const debug = process.env.DEBUG === "true";
const maxItems = Number(process.env.MAX_ITEMS ?? 50);

.env files, without a package#

Typing variables into the terminal every time is tedious, so on your own computer they usually live in a .env file in the project folder:

Shell
# .env
PORT=3000
DATABASE_URL=postgres://localhost:5432/shop
PAYMENT_API_KEY=sk_test_123

Node.js 20.6 and newer can load it directly, with no extra package:

Shell
node --env-file=.env server.js

Put it in your package.json scripts so nobody has to remember the flag:

JSON
{
  "scripts": {
    "dev": "node --watch --env-file=.env server.js"
  }
}

Older projects often use the dotenv package for the same job. It still works; for new projects on a current Node.js version, the built-in flag means one less dependency.

Never commit .env#

The .env file holds real secrets, so it must not go into Git. Add it to .gitignore before your first commit:

.env
.env.*
!.env.example

Then commit a .env.example with the same names and no real values, so others know what to set:

Shell
# .env.example
PORT=3000
DATABASE_URL=
PAYMENT_API_KEY=

If a real key was ever committed, removing the file isn’t enough: it’s still in the Git history, and anyone who cloned or forked the repository may have it. Rotate the key (create a new one and delete the old) in the provider’s dashboard.

Check them at startup#

A missing variable often shows up as a confusing error much later, such as “401 Unauthorized” from an API. Check the required ones when the app starts, and stop with a clear message:

JavaScript
// config.js
const required = ["DATABASE_URL", "PAYMENT_API_KEY"];
const missing = required.filter((name) => !process.env[name]);

if (missing.length) {
  console.error(`Missing environment variables: ${missing.join(", ")}`);
  process.exit(1);
}

export const config = {
  port: Number(process.env.PORT || 3000),
  databaseUrl: process.env.DATABASE_URL,
  paymentApiKey: process.env.PAYMENT_API_KEY,
};

Import config everywhere instead of reading process.env all over the code. It’s also the one place to see every setting the app depends on.

In production#

On the live server you don’t upload a .env file. Hosting platforms such as Vercel, Render, Railway and Fly.io have a settings page for environment variables, and they give them to your app as it starts. Set different values for each environment: test keys for staging, live keys for production.

Frontend code can’t keep secrets#

Frontend build tools also read environment variables, but only to paste their values into the JavaScript sent to the browser. Vite, for example, only exposes variables that start with VITE_, precisely because anything exposed becomes public. Any visitor can read it in the browser’s developer tools.

So a secret key never belongs in React or other browser code, even “hidden” in an environment variable. Make the call from your server instead, as in building a REST API with Node.js and Express, and have the browser call your server.

On Wix, the equivalent of environment variables for secrets is the Secrets Manager, read from backend code with getSecret. How to call an external API from Wix Velo shows how.

If you need API keys and integrations set up safely, I do custom API integration.

One email when a new post is published. Unsubscribe any time.

Comments