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:
node report.js sales.csv --month=3const 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:
PORT=8080 node server.js # macOS and Linux
$env:PORT=8080; node server.js # Windows PowerShellconst port = Number(process.env.PORT ?? 3000);- Every value in
process.envis a string orundefined, so convert numbers and booleans yourself. - During development, keep settings in a
.envfile and load it withnode --env-file=.env server.js(Node 20.6+), or callprocess.loadEnvFile()in code. Older projects use thedotenvpackage for the same job. - Add
.envto.gitignore, and commit a.env.examplelisting the variable names with dummy values, so others know what to set.
Exit codes, signals and other details
- An exit code of
0means success; anything else means failure. Setprocess.exitCode = 1and let the program finish, rather than callingprocess.exit(1)immediately, which can cut off output that hasn't been written yet. process.cwd()is the current working directory;process.platformandprocess.versiondescribe the environment;process.memoryUsage()reports memory.- Servers receive
SIGTERMwhen a hosting platform stops them andSIGINTwhen you press Ctrl+C. Listen withprocess.on('SIGTERM', ...)to close connections cleanly. process.stdout.writeprints without a new line;process.stdinreads input, whichreadlinemakes 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:
cat orders.csv | node count-lines.jsprocess.stdin is a readable stream, so you can loop over the chunks with for await:
let text = '';
for await (const chunk of process.stdin) {
text += chunk;
}
console.log(text.split('\n').filter(Boolean).length); // 3 for a three-line fileThe 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.
Parsing command-line options
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}`);
}[ 'sales.csv' ]
3 true json
Report for sales.csv, month 3Line-by-line explanation
- Line 4args is given here for the demo; normally parseArgs reads process.argv automatically.
- Lines 6 to 8Each option has a type, and can have a short form and a default.
- Line 10allowPositionals accepts plain values like the file name.
- Line 16Option values are strings, so they're converted and checked.
- Line 19Setting exitCode reports failure to the shell without stopping the program abruptly.
A configuration module with defaults
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);
}{
port: 8080,
dbUrl: 'postgres://localhost/shop',
searchEnabled: true,
logLevel: 'info'
}
Missing environment variable PAYMENT_API_KEYLine-by-line explanation
- Line 3The demo writes a .env file; in a real project it already exists and isn't committed.
- Line 4loadEnvFile copies the file's values into process.env (Node 20.12 and later).
- Lines 6 to 10A helper fails fast with a clear message when a required setting is missing.
- Line 13Environment values are strings, so the port is converted to a number.
- Line 15Booleans are compared as text.
- Line 16?? gives a default when the variable isn't set.
Graceful shutdown
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;
});
});Server started
SIGTERM received, closing server
All connections closed, exiting cleanlyLine-by-line explanation
- Line 5Port 0 asks the system for any unused port, which is handy in demos and tests.
- Line 7The demo sends itself SIGTERM; on a real server the hosting platform sends it.
- Line 10The listener runs when the signal arrives.
- Line 12close stops accepting new connections and waits for current ones to finish.
Common Node.js Basics Mistakes
Secrets in the source code
const stripe = new Stripe('sk_live_51Hx...'); // the key ends up in Git historyconst stripe = new Stripe(process.env.STRIPE_SECRET_KEY);Treating env values as numbers or booleans
if (process.env.DEBUG) { } // "false" is a non-empty string, so this is true
const port = process.env.PORT + 1; // "80801"const debug = process.env.DEBUG === 'true';
const port = Number(process.env.PORT) + 1;Calling process.exit too early
console.log(bigReport);
process.exit(1); // output may be cut offconsole.log(bigReport);
process.exitCode = 1; // the program ends naturally with the failure codeNode.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.
Complete the line to read the PORT environment variable.
const port = Number(process..PORT ?? 3000);
What type are values in process.env?
The program is run with node app.js report.csv 5. What does this print?
console.log(process.argv.slice(2).length);
Which exit code means success?
Why listen for SIGTERM in a server?
Node.js Basics Quiz
5 questions to check what you learned.
Where should API keys for a server be stored?
- In the source code
- In package.json
- In environment variables
- In a comment
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
What does process.cwd() return?
- The current working directory
- The script's folder
- The home folder
- The Node version
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
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