Node.js process and Environment Variables

Control and configure Node.js programs with the process object: read command-line arguments, use environment variables and .env files for settings and secrets, set exit codes, read input, and shut down gracefully.

Lesson 7 of 43Beginner18 min read

A program rarely runs in exactly one way. A command-line tool needs to know which file to process. A web server needs to know which port to listen on, which database to connect to and which API keys to use, and those values are different on your laptop, on a test server and in production. Hard-coding them into the source would mean editing code every time you deploy, and it would put passwords into your Git history.

Node gives every program a global process object that connects it to the outside world. process.argv holds the command-line arguments. process.env holds environment variables, the standard way to configure servers and pass secrets. process.exitCode tells the shell whether the program succeeded, and process.on('SIGTERM') lets a server finish its work before shutting down.

In this lesson you'll read arguments and environment variables, load settings from a .env file using Node's built-in support, build a small configuration module with defaults and validation, set exit codes properly, and handle shutdown signals.

Command-line arguments

process.argv is an array of strings. The first item is the path to Node, the second is the script, and anything you typed after the script follows:

Terminal
node report.js sales.csv --month=3
JavaScript
const args = process.argv.slice(2);   // ['sales.csv', '--month=3']

For anything beyond the simplest tools, use parseArgs from node:util, which understands flags like --verbose, --month 3 and short options like -v, and reports unknown options.

Environment variables and .env files

Environment variables are named text values set outside your program:

Terminal
PORT=8080 node server.js                 # macOS and Linux
$env:PORT=8080; node server.js           # Windows PowerShell
JavaScript
const port = Number(process.env.PORT ?? 3000);
  • Every value in process.env is a string or undefined, so convert numbers and booleans yourself.
  • During development, keep settings in a .env file and load it with node --env-file=.env server.js (Node 20.6+), or call process.loadEnvFile() in code. Older projects use the dotenv package for the same job.
  • Add .env to .gitignore, and commit a .env.example listing the variable names with dummy values, so others know what to set.

Exit codes, signals and other details

  • An exit code of 0 means success; anything else means failure. Set process.exitCode = 1 and let the program finish, rather than calling process.exit(1) immediately, which can cut off output that hasn't been written yet.
  • process.cwd() is the current working directory; process.platform and process.version describe the environment; process.memoryUsage() reports memory.
  • Servers receive SIGTERM when a hosting platform stops them and SIGINT when you press Ctrl+C. Listen with process.on('SIGTERM', ...) to close connections cleanly.
  • process.stdout.write prints without a new line; process.stdin reads input, which readline makes easy.

Reading piped input from stdin

Command-line tools often read data that another program sends them, so they can be chained together in the shell:

Terminal
cat orders.csv | node count-lines.js

process.stdin is a readable stream, so you can loop over the chunks with for await:

JavaScript
let text = '';
for await (const chunk of process.stdin) {
  text += chunk;
}
console.log(text.split('\n').filter(Boolean).length);   // 3 for a three-line file

The loop finishes when the other program closes its output. If nobody pipes anything in, the script waits for you to type and press Ctrl+D (Ctrl+Z then Enter on Windows). Tools usually check process.stdin.isTTY first: it's true when input comes from a terminal and undefined when it's piped, so you can print usage instructions instead of sitting there silently.

Node.js Basics Syntax

process essentials

const [, , ...args] = process.argv;
import { parseArgs } from 'node:util';
const { values } = parseArgs({ options: { verbose: { type: 'boolean', short: 'v' } } });
const port = Number(process.env.PORT ?? 3000);
const debug = process.env.DEBUG === 'true';
process.loadEnvFile('.env');            // or: node --env-file=.env app.js
process.exitCode = 1;
process.cwd();   process.platform;   process.memoryUsage().heapUsed;
process.on('SIGTERM', () => server.close());

Node.js Basics Summary

  • process.argv holds command-line arguments; parseArgs from node:util parses flags.
  • process.env holds environment variables, always as strings or undefined.
  • node --env-file=.env loads a .env file; never commit real secrets.
  • Set process.exitCode to report failure; 0 means success.
  • Listen for SIGTERM and SIGINT to shut down servers gracefully.

Node.js Basics Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Parsing command-line options

Node.js
import { parseArgs } from 'node:util';

const { values, positionals } = parseArgs({
  args: ['sales.csv', '--month', '3', '-v', '--format=json'],
  options: {
    month: { type: 'string' },
    verbose: { type: 'boolean', short: 'v', default: false },
    format: { type: 'string', default: 'table' },
  },
  allowPositionals: true,
});

console.log(positionals);
console.log(values.month, values.verbose, values.format);

const month = Number(values.month);
if (!Number.isInteger(month) || month < 1 || month > 12) {
  console.log('Month must be 1 to 12');
  process.exitCode = 1;
} else {
  console.log(`Report for ${positionals[0]}, month ${month}`);
}
Output
[ 'sales.csv' ]
3 true json
Report for sales.csv, month 3
Line-by-line explanation
  1. Line 4args is given here for the demo; normally parseArgs reads process.argv automatically.
  2. Lines 6 to 8Each option has a type, and can have a short form and a default.
  3. Line 10allowPositionals accepts plain values like the file name.
  4. Line 16Option values are strings, so they're converted and checked.
  5. Line 19Setting exitCode reports failure to the shell without stopping the program abruptly.
Example 2

A configuration module with defaults

Node.js
import { writeFile } from 'node:fs/promises';

await writeFile('.env.demo', 'PORT=8080\nDB_URL=postgres://localhost/shop\nFEATURE_SEARCH=true\n');
process.loadEnvFile('.env.demo');

function required(name) {
  const value = process.env[name];
  if (!value) throw new Error(`Missing environment variable ${name}`);
  return value;
}

const config = {
  port: Number(process.env.PORT ?? 3000),
  dbUrl: required('DB_URL'),
  searchEnabled: process.env.FEATURE_SEARCH === 'true',
  logLevel: process.env.LOG_LEVEL ?? 'info',
};
console.log(config);

try {
  required('PAYMENT_API_KEY');
} catch (err) {
  console.log(err.message);
}
Output
{
  port: 8080,
  dbUrl: 'postgres://localhost/shop',
  searchEnabled: true,
  logLevel: 'info'
}
Missing environment variable PAYMENT_API_KEY
Line-by-line explanation
  1. Line 3The demo writes a .env file; in a real project it already exists and isn't committed.
  2. Line 4loadEnvFile copies the file's values into process.env (Node 20.12 and later).
  3. Lines 6 to 10A helper fails fast with a clear message when a required setting is missing.
  4. Line 13Environment values are strings, so the port is converted to a number.
  5. Line 15Booleans are compared as text.
  6. Line 16?? gives a default when the variable isn't set.
Example 3

Graceful shutdown

Node.js
import { createServer } from 'node:http';

const server = createServer((req, res) => res.end('ok'));

server.listen(0, () => {
  console.log('Server started');
  process.emit('SIGTERM');
});

process.on('SIGTERM', () => {
  console.log('SIGTERM received, closing server');
  server.close(() => {
    console.log('All connections closed, exiting cleanly');
    process.exitCode = 0;
  });
});
Output
Server started
SIGTERM received, closing server
All connections closed, exiting cleanly
Line-by-line explanation
  1. Line 5Port 0 asks the system for any unused port, which is handy in demos and tests.
  2. Line 7The demo sends itself SIGTERM; on a real server the hosting platform sends it.
  3. Line 10The listener runs when the signal arrives.
  4. Line 12close stops accepting new connections and waits for current ones to finish.

Common Node.js Basics Mistakes

Secrets in the source code

Don't
const stripe = new Stripe('sk_live_51Hx...');   // the key ends up in Git history
Do
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

Treating env values as numbers or booleans

Don't
if (process.env.DEBUG) { }        // "false" is a non-empty string, so this is true
const port = process.env.PORT + 1;   // "80801"
Do
const debug = process.env.DEBUG === 'true';
const port = Number(process.env.PORT) + 1;

Calling process.exit too early

Don't
console.log(bigReport);
process.exit(1);                  // output may be cut off
Do
console.log(bigReport);
process.exitCode = 1;             // the program ends naturally with the failure code

Node.js Basics in Real Projects

process and env in production

Hosting platforms such as Render, Railway, Fly.io, Heroku and AWS set PORT, NODE_ENV and your secrets as environment variables, managed in their dashboards. Docker and Kubernetes pass them into containers. CI pipelines use exit codes to decide whether a build passed, and servers listen for SIGTERM so deployments can replace old instances without dropping requests.

Node.js Basics FAQ

How do I read environment variables in Node.js?

Use process.env.NAME, for example process.env.DATABASE_URL. The value is always a string, or undefined if the variable isn't set, so convert it yourself: Number(process.env.PORT ?? 3000) or process.env.DEBUG === 'true'. Read them in one configuration module so the rest of the code doesn't touch process.env directly.

Do I still need the dotenv package?

Not for new projects on Node 20.6 or later. node --env-file=.env app.js loads the file before your code runs, and process.loadEnvFile() loads it from inside your code. dotenv is still common in older projects and works fine, so there's no urgency to remove it.

What does NODE_ENV=production do?

Node itself doesn't change its behaviour based on NODE_ENV; it's a convention that libraries and your own code read. Express, for example, caches view templates when it's set to production, and many tools skip development-only checks. npm install and npm ci also skip devDependencies when it's production, which is worth knowing when a build step suddenly can't find a tool.

What is the difference between process.exit() and process.exitCode?

process.exit(code) stops the program immediately, which can lose output that hasn't been flushed and skip pending work like closing a database connection. Setting process.exitCode = 1 records the failure and lets Node exit naturally once there's nothing left to do. Prefer exitCode, and keep process.exit for when you genuinely need to stop right away.

How do I handle Ctrl+C in a Node.js script?

Listen for the SIGINT signal: process.on('SIGINT', () => { ... }). Once you add a listener, Node no longer exits on Ctrl+C by itself, so your handler must finish its cleanup and then let the program end, for example by closing the server and setting process.exitCode. Hosting platforms send SIGTERM instead, so servers usually handle both.

Node.js Basics Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the line to read the PORT environment variable.

const port = Number(process..PORT ?? 3000);
Multiple choiceeasy

What type are values in process.env?

Predict the outputmedium

The program is run with node app.js report.csv 5. What does this print?

console.log(process.argv.slice(2).length);
Multiple choicemedium

Which exit code means success?

Multiple choicehard

Why listen for SIGTERM in a server?

Node.js Basics Quiz

5 questions to check what you learned.

  1. Where should API keys for a server be stored?

    • In the source code
    • In package.json
    • In environment variables
    • In a comment
  2. How do you load a .env file with Node's built-in support?

    • node --dotenv
    • import '.env'
    • npm env
    • node --env-file=.env app.js
  3. What does process.cwd() return?

    • The current working directory
    • The script's folder
    • The home folder
    • The Node version
  4. What should be committed instead of a real .env file?

    • Nothing
    • A .env.example with variable names and dummy values
    • The production .env
    • A zip of .env
  5. Why is if (process.env.ENABLED) a bug when ENABLED is "false"?

    • "false" is a non-empty string, which is truthy
    • It isn't
    • process.env is undefined
    • It throws an error