Read environment variables in Node.js

Load and validate environment variables, use .env files deliberately, and keep credentials out of logs and browser bundles.

Best practices
Illustration of an event loop and asynchronous tasks

Read an environment variable through process.env. A missing variable is undefined; an existing variable normally has a string value.

const apiKey = process.env.API_KEY;
if (!apiKey) {
  throw new Error('API_KEY is required');
}
console.log('API key configured');

Check that a secret is present without printing its value. Decide explicitly whether an empty string is allowed for each setting.

Supply variables at launch

Use a non-secret setting to try the shell syntax:

# POSIX shell: retained for later child processes
export APP_MODE=development
node app.cjs

# POSIX shell: this command only
APP_MODE=development node app.cjs
$env:APP_MODE = 'development'
node app.cjs

Shell history, process diagnostics, and child-process inheritance can expose environment-delivered secrets. Production credentials should come through an appropriately controlled secret-delivery mechanism.

Load a local .env file

A development file might contain:

APP_MODE=development
DB_URL=postgres://localhost:5432/mydb

Node added --env-file in 20.6:

node --env-file=.env app.cjs

The path is relative to the working directory. Existing process environment values take precedence over values in the file. Check your Node release when using newer parsing features.

For runtimes or projects using dotenv, install it locally and load it before modules read configuration:

require('dotenv').config();

With ESM, initialization order matters; preloading dotenv or using its configuration entry point avoids reading variables before they are loaded. dotenv is a loader, not a file watcher.

Validate once at startup

for (const name of ['API_KEY', 'DB_URL']) {
  if (!process.env[name]) throw new Error(`${name} is required`);
}
const rawPort = process.env.PORT ?? '3000';
if (!/^\d+$/.test(rawPort)) throw new Error('PORT must be an integer');
const port = Number(rawPort);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
  throw new Error('PORT must be between 1 and 65535');
}
const debug = process.env.DEBUG === 'true';

The string 'false' is truthy, so ordinary boolean coercion is unsuitable for a flag. For settings with more than two accepted values, reject unexpected input rather than silently converting it.

Add secret-bearing .env files to .gitignore and commit a sanitized .env.example. Ignoring a file does not remove an already tracked secret; revoke exposed credentials and address repository history separately.

Changing the file does not update a running process automatically. Restart the application or implement deliberate reload behavior. A browser has no native process.env; values exposed by frontend build tools become client-accessible code. Never put a server secret in a client-exposed variable. Object.keys(process.env) can enumerate names, but avoid dumping the environment into logs.

References

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting