|$ curl https://forge-ai.dev/api/markdown?path=docs/nodejs/path-os
$cat docs/path,-os-&-process.md
updated Recently·20-28 min read·published

path, os & process

Node.jsProcessOSIntermediate🎯Free Tools
Introduction

Every Node.js process runs inside an operating system with a specific filesystem layout, CPU count, memory budget, and environment. Three built-in modules — path, os, and the global process object — give you portable access to these details without adding dependencies.

The path module hides the difference between Windows backslashes and POSIX forward slashes. The os module exposes machine-level facts such as CPU count, total memory, and the temporary directory. The process object connects your code to the outside world through command-line arguments, environment variables, signals, and lifecycle events.

This guide covers real APIs, real code, and production concerns: handling paths safely across platforms, reading environment variables, parsing command-line flags, reacting to process signals, avoiding memory leaks in timers, and bridging the gap between CommonJS and ESM when you need __dirname in an ES module.

The path Module

Hard-coding / or \\ separators is one of the most common causes of cross-platform bugs. Node.js path normalizes separators automatically and provides helpers for joining, resolving, parsing, and comparing paths.

join vs resolve

path.join concatenates path fragments using the platform separator. path.resolve returns an absolute path by processing segments from right to left and prepending the current working directory if the result is still relative.

path-join-resolve.js
JavaScript
1const path = require('path');
2
3// join: relative concatenation with platform separators
4const logs = path.join('var', 'log', 'app.log');
5// On POSIX: 'var/log/app.log'
6// On Windows: 'var\log\app.log'
7
8// resolve: absolute path from CWD or an explicit base
9const absolute = path.resolve('var', 'log', 'app.log');
10// /home/user/project/var/log/app.log
11
12// resolve ignores earlier segments once an absolute segment appears
13path.resolve('/etc', 'app', 'config.json');
14// /etc/app/config.json
15
16path.resolve('src', '/opt', 'data');
17// /opt/data

info

Use path.join for building relative paths inside your project. Use path.resolve when you need an absolute path for filesystem APIs, container mounts, or log aggregation agents.

normalize and parse

User-supplied paths often contain redundant segments such as .., ., or double separators. path.normalize collapses those without touching the filesystem. path.parse decomposes a path into root, directory, base, name, and extension.

path-normalize-parse.js
JavaScript
1const path = require('path');
2
3const messy = './data//users/../orders/./invoice.pdf';
4console.log(path.normalize(messy));
5// 'data/orders/invoice.pdf'
6
7const parsed = path.parse('/var/www/html/index.html');
8console.log(parsed);
9// {
10// root: '/',
11// dir: '/var/www/html',
12// base: 'index.html',
13// ext: '.html',
14// name: 'index'
15// }
16
17console.log(path.extname('archive.tar.gz'));
18// '.gz' (only last extension)
19
20console.log(path.basename('/tmp/file.txt', '.txt'));
21// 'file'
MethodPurposeExample Output
path.join(...)Concatenate path segmentsa/b/c.txt
path.resolve(...)Absolute path from segments/project/a/b/c.txt
path.normalize(p)Collapse ., .. and double separatorsclean/path
path.parse(p)Decompose into root/dir/base/ext/nameobject
path.extname(p)Last extension only.txt
path.relative(from, to)Relative path between two paths../b/file.js

warning

path.normalize does not protect against path traversal. A value like ../etc/passwd will still resolve upward. Always validate user input and whitelist allowed directories before opening files.
Cross-Platform Path Handling

Windows uses drive letters and backslashes; POSIX systems use a single root and forward slashes. Node.js defaults to the platform-specific implementation, but path.posix and path.win32 let you force one format. This is essential when generating URLs, tar archives, or container image layers from a Windows developer machine.

cross-platform-paths.js
JavaScript
1const path = require('path');
2
3// Force POSIX-style paths regardless of OS
4const posix = path.posix.join('src', 'components', 'Button.tsx');
5// 'src/components/Button.tsx'
6
7// Force Windows-style paths
8const win = path.win32.join('src', 'components', 'Button.tsx');
9// 'src\components\Button.tsx'
10
11// Prefer url.pathToFileURL for correctness on all platforms
12const { pathToFileURL } = require('url');
13console.log(pathToFileURL('/etc/hosts').href);
14// file:///etc/hosts

best practice

Use path.posix when computing paths that will be stored in lockfiles, ZIP entries, or source maps. Use url.pathToFileURL instead of manual string concatenation when converting local paths to URLs.
The os Module

The os module provides read-only information about the machine and operating system. It is useful for sizing worker pools, choosing cache directories, logging platform-specific diagnostics, and deciding whether to enable memory-intensive features.

os-module.js
JavaScript
1const os = require('os');
2
3console.log(os.platform()); // 'linux' | 'darwin' | 'win32'
4console.log(os.release()); // kernel version
5console.log(os.arch()); // 'x64' | 'arm64'
6console.log(os.cpus().length); // number of logical cores
7
8// Memory in bytes — useful for cache sizing
9const total = os.totalmem();
10const free = os.freemem();
11console.log(`Used: ${((total - free) / 1024 / 1024 / 1024).toFixed(2)} GB / ${(total / 1024 / 1024 / 1024).toFixed(2)} GB`);
12
13// Well-known directories
14console.log(os.homedir()); // /home/alice
15console.log(os.tmpdir()); // /tmp
16console.log(os.hostname()); // laptop-alice
APIReturnsCommon Use
os.platform()'linux', 'darwin', 'win32', ...Conditional platform logic
os.arch()'x64', 'arm64', ...Binary selection, feature flags
os.cpus()Array of CPU objectsWorker pool sizing
os.totalmem()BytesCache budget, memory warnings
os.freemem()BytesHealth checks, backpressure
os.homedir()User home pathConfig and credential storage
os.tmpdir()Temp directory pathUpload buffers, extraction

info

Do not use os.cpus().length blindly for thread pools inside containers. Kubernetes CPU limits may expose fewer cores than the host. Read /sys/fs/cgroup/cpu/cpu.cfs_quota_us or use libraries such as physical-cpu-count when exact sizing matters.
process Basics

The global process object is an instance of EventEmitter that represents the current Node.js process. It exposes command-line arguments, environment variables, the current working directory, process IDs, and methods to terminate or inspect the process.

process-basics.js
JavaScript
1// process basics
2console.log(process.pid); // process ID
3console.log(process.ppid); // parent process ID
4console.log(process.cwd()); // current working directory
5console.log(process.title); // process title (can be set)
6console.log(process.version); // Node.js version string
7console.log(process.versions); // versions of V8, libuv, zlib, etc.
8
9// argv[0] is node executable, argv[1] is the script
10// argv[2+] are user arguments
11console.log(process.argv);
12// ['node', '/app/bin/cli.js', '--port', '3000']
13
14// Environment variables are read from the OS environment
15console.log(process.env.NODE_ENV);
16console.log(process.env.PATH);

Parsing argv

For simple scripts you can parse process.argv manually. For production CLIs, prefer libraries like commander, yargs, or Node.js 20's built-in util.parseArgs which supports boolean flags, string options, and multiple values.

parse-args.js
JavaScript
1const { parseArgs } = require('util');
2
3const options = {
4 port: { type: 'string', short: 'p', default: '3000' },
5 verbose: { type: 'boolean', short: 'v', default: false },
6 env: { type: 'string', multiple: true },
7};
8
9const { values, positionals } = parseArgs({ args: process.argv.slice(2), options });
10
11console.log(values.port); // '8080'
12console.log(values.verbose); // true
13console.log(values.env); // ['staging', 'debug']
14console.log(positionals); // remaining positional args

best practice

Validate and coerce parsed arguments immediately. A port value of 3000 from parseArgs is still a string; convert it with Number.parseInt before passing it to a server.
Environment Variables

Environment variables are the standard way to inject configuration into Node.js applications without changing code. They work across clouds, containers, and CI systems, and they keep secrets out of source control. Read them from process.env once at startup and validate them with a schema.

env-config.js
JavaScript
1// config.js — load and validate env once
2const port = Number.parseInt(process.env.PORT || '3000', 10);
3const nodeEnv = process.env.NODE_ENV || 'development';
4const databaseUrl = process.env.DATABASE_URL;
5
6if (!databaseUrl) {
7 console.error('DATABASE_URL is required');
8 process.exit(1);
9}
10
11if (Number.isNaN(port) || port < 1 || port > 65535) {
12 console.error('PORT must be a valid TCP port');
13 process.exit(1);
14}
15
16module.exports = { port, nodeEnv, databaseUrl };

Loading a .env file is convenient for local development, but it should never be used in production. Docker, Kubernetes, and cloud secret managers are safer places for secrets. When you do load a local file, use dotenv before any other imports so that the rest of your application sees the values immediately.

dotenv-entry.js
JavaScript
1// index.js — load .env first
2require('dotenv').config();
3
4const config = require('./config');
5const server = require('./server');
6
7server.listen(config.port, () => {
8 console.log(`Listening on port ${config.port}`);
9});
10
11// package.json script
12// "start": "node -r dotenv/config index.js"

warning

Never commit .env files. Add .env* to .gitignore. In production, rely on orchestrator-injected secrets, not files checked into the repository.
Exit Codes and process.exit

A Node.js process exits when the event loop has no more work, or when process.exit(code) is called. Exit code 0 means success; any non-zero value indicates failure. Shell scripts, systemd, Kubernetes, and CI systems all interpret this code.

exit-codes.js
JavaScript
1// Bad: exit immediately while async work is pending
2process.exit(1);
3
4// Good: set exit code and let the event loop drain
5process.exitCode = 1;
6
7async function main() {
8 try {
9 await connectDatabase();
10 await startServer();
11 } catch (err) {
12 console.error('Startup failed:', err);
13 process.exitCode = 1;
14 }
15}
16
17main();
Exit CodeMeaning
0Success
1Uncaught fatal exception or general failure
3Internal JavaScript parse error
4Internal JavaScript evaluation failure
128 + nFatal error signal n (e.g., SIGKILL = 137)

best practice

Prefer setting process.exitCode over calling process.exit(). The former lets streams flush, promises settle, and cleanup handlers run. Only call process.exit() when you need immediate termination and accept the risk of losing buffered logs.
Signals and Graceful Shutdown

Operating systems communicate with processes through signals. Node.js can listen for SIGINT (Ctrl+C), SIGTERM (polite shutdown), and SIGUSR2 (user-defined). A well-behaved server closes its HTTP listener, drains in-flight requests, flushes logs, and then exits cleanly.

graceful-shutdown.js
JavaScript
1const http = require('http');
2
3const server = http.createServer((req, res) => {
4 res.end('ok');
5});
6
7const connections = new Set();
8server.on('connection', (conn) => {
9 connections.add(conn);
10 conn.once('close', () => connections.delete(conn));
11});
12
13server.listen(3000, () => console.log('Server running'));
14
15async function shutdown(signal) {
16 console.log(`Received ${signal}, shutting down gracefully...`);
17
18 server.close((err) => {
19 if (err) console.error('Error closing server:', err);
20 });
21
22 for (const conn of connections) {
23 conn.destroy();
24 }
25
26 await closeDatabase();
27 console.log('Cleanup complete');
28 process.exit(0);
29}
30
31process.on('SIGTERM', () => shutdown('SIGTERM'));
32process.on('SIGINT', () => shutdown('SIGINT'));
33
34// Handle uncaught exceptions before crashing
35process.on('uncaughtException', (err) => {
36 console.error('Uncaught exception:', err);
37 shutdown('uncaughtException').catch(() => process.exit(1));
38});

warning

SIGKILL cannot be caught or ignored. Kubernetes sends SIGTERM first, waits a grace period (default 30s), then sends SIGKILL. Design your shutdown to finish faster than that grace period.
Timing and Process Metrics

Node.js provides several ways to measure time. Date.now() is wall-clock time in milliseconds. process.hrtime.bigint() returns a monotonic high-resolution timestamp in nanoseconds that is not affected by system clock changes. process.uptime() tells you how long the process has been alive.

timing-metrics.js
JavaScript
1const start = process.hrtime.bigint();
2await expensiveOperation();
3const elapsedNs = process.hrtime.bigint() - start;
4console.log(`Operation took ${Number(elapsedNs) / 1e6} ms`);
5
6// process.uptime() returns seconds since process start
7setInterval(() => {
8 console.log(`Uptime: ${process.uptime().toFixed(2)}s`);
9}, 5000);
10
11// Resource usage and memory snapshot
12console.log(process.resourceUsage());
13console.log(process.memoryUsage());
14// { rss, heapTotal, heapUsed, external, arrayBuffers, ... }

info

Use process.hrtime.bigint() for latency benchmarks. It is monotonic and has nanosecond precision, unlike Date.now() which can jump backward if the system clock is adjusted.
ESM __dirname Workarounds

CommonJS modules have __dirname and __filename variables. ES modules do not. If you are using .mjs files or "type": "module" in package.json, you can recreate these values with import.meta.url and the url and path modules.

esm-dirname.mjs
JavaScript
1import { fileURLToPath } from 'url';
2import { dirname, join } from 'path';
3
4const __filename = fileURLToPath(import.meta.url);
5const __dirname = dirname(__filename);
6
7// Now you can resolve relative assets
8const templatePath = join(__dirname, '..', 'templates', 'email.html');
9console.log(templatePath);

In Node.js 20.11 and later, you can use the built-in import.meta.dirname and import.meta.filename properties without any helper imports. This removes boilerplate and reduces the chance of path bugs.

import-meta-dirname.mjs
JavaScript
1// Node.js 20.11+
2import { join } from 'path';
3
4const templatePath = join(import.meta.dirname, '..', 'templates', 'email.html');
5console.log(import.meta.filename); // absolute path to current file
6console.log(import.meta.dirname); // absolute path to current directory

best practice

If your project targets Node.js 20.11+, prefer import.meta.dirname. For broader compatibility, define __dirname once in a small utility module and import it wherever you need to resolve relative paths.
Common Mistakes and Best Practices

Small mistakes around paths, environment variables, and process lifecycle cause large production issues: container restarts, leaked file descriptors, security holes, and silent configuration drift. The following patterns help you avoid the most common pitfalls.

Validate all environment variables at startup

Fail fast if a required variable is missing. Do not sprinkle process.env.SOME_VAR reads throughout your codebase; centralize them in a single config module.

Never trust process.cwd()

The current working directory depends on where the process was started, not where the source file lives. Resolve project paths with __dirname or import.meta.dirname instead of assuming process.cwd().

Use path.posix for deterministic output

Tools that generate source maps, manifest files, or container layers should use path.posix.joinso that output is identical regardless of the developer's operating system.

shutdown-timeout.js
JavaScript
1// Robust shutdown with a safety timeout
2async function gracefulShutdown(server) {
3 const forceExit = setTimeout(() => {
4 console.error('Forced shutdown after timeout');
5 process.exit(1);
6 }, 25000).unref();
7
8 await new Promise((resolve) => server.close(resolve));
9 await closeDatabasePool();
10
11 clearTimeout(forceExit);
12 process.exit(0);
13}
$Blueprint — Engineering Documentation·Section ID: NODE-PATH-01·Revision: 1.0