# The `process` Object and Environment Variables

## 1. `process` — the Global Handle on the Running Program

`process` is a global object (no `require()` needed) that represents the currently running Node instance itself — its arguments, environment, standard streams, and lifecycle.

```js
console.log(process.pid);        // this process's OS process ID
console.log(process.platform);   // "darwin", "linux", "win32", ...
console.log(process.version);    // "v20.11.0" — the Node version running this code
console.log(process.cwd());      // the current working directory the process was launched from
```

## 2. `process.argv` — Command-Line Arguments

```js
// node script.js foo --bar=baz
console.log(process.argv);
// [ '/usr/local/bin/node', '/path/to/script.js', 'foo', '--bar=baz' ]

const userArgs = process.argv.slice(2); // ['foo', '--bar=baz'] — the actual args YOU care about
```

`argv[0]` is always the path to the Node executable itself, `argv[1]` is the script path — real user-supplied arguments start at index 2. Parsing flags like `--bar=baz` manually is normal for small scripts; larger CLIs typically use a parsing library, but the raw array is always there underneath.

## 3. `process.env` — Reading the Environment

```js
console.log(process.env.HOME);      // e.g. "/Users/ana"
console.log(process.env.PATH);      // the shell PATH string
console.log(process.env.NODE_ENV);  // undefined unless explicitly set

const port = Number(process.env.PORT) || 3000; // common pattern: env override with a fallback default
```

Every value in `process.env` is a **string** — even something that looks numeric or boolean was set as text by whatever launched the process (a shell, a `.env` file loader, a container orchestrator, a CI system). `process.env.DEBUG === "false"` is a truthy, non-empty string; you must parse it yourself (`=== "true"`, `Number(...)`, etc.) if you need a real boolean or number.

```js
process.env.MY_VAR = "hello"; // you CAN write to process.env — but it only affects THIS process and its children
console.log(process.env.MY_VAR); // "hello"
```

## 4. Standard Streams and Exit

```js
process.stdout.write("no newline added automatically\n"); // lower-level than console.log
process.stderr.write("goes to stderr, not stdout\n");

process.stdin.on("data", (chunk) => {
  console.log("received:", chunk.toString().trim());
});
```

`console.log`/`console.error` are convenience wrappers around `process.stdout`/`process.stderr` (with a newline and formatting added); `process.stdout`/`stderr` are themselves `Writable` streams (see the Streams topic), and `process.stdin` is a `Readable`.

```js
process.exit(0);  // exit immediately, code 0 = success
process.exit(1);  // exit immediately, any nonzero = failure (convention)
```

`process.exit()` terminates the process **immediately**, without waiting for pending I/O (a scheduled `fs.writeFile` callback that hasn't fired yet may simply never run). Prefer letting the event loop drain naturally (removing all pending work causes Node to exit on its own with code 0) and reserve explicit `process.exit(code)` for genuine early-abort scenarios (a fatal startup error, a CLI tool's usage of specific exit codes).

## 5. Process Lifecycle Events

```js
process.on("exit", (code) => {
  console.log(`about to exit with code ${code}`); // ONLY synchronous work allowed here
});

process.on("uncaughtException", (err) => {
  console.error("uncaught:", err);
  process.exit(1); // best practice: log and exit — the process is in an unknown state after this
});

process.on("unhandledRejection", (reason) => {
  console.error("unhandled rejection:", reason);
});
```

`'exit'` fires synchronously right before the process actually terminates — no async work (a Promise, a timer, an I/O call) started inside it will ever run. `'uncaughtException'`/`'unhandledRejection'` are last-resort safety nets, not routine error handling — after an uncaught exception, application state may be inconsistent (a half-updated cache, a partially-written file), so the conventional advice is to log and exit rather than try to keep running.

## 6. Signals

```js
process.on("SIGINT", () => {   // Ctrl+C in a terminal
  console.log("caught SIGINT, cleaning up...");
  process.exit(0);
});

process.on("SIGTERM", () => {  // typical "please shut down gracefully" signal (e.g. from Docker/Kubernetes)
  console.log("caught SIGTERM, shutting down gracefully...");
  server.close(() => process.exit(0));
});
```

Registering a handler for `SIGINT`/`SIGTERM` lets a program run cleanup logic (closing database connections, finishing in-flight requests) before exiting, instead of being killed abruptly — this is the standard graceful-shutdown pattern for long-running servers.

![process.env is a plain object copied from the OS environment at spawn time; process.argv is a fixed array; process itself is an EventEmitter for lifecycle events and signals](diagrams/01-process-anatomy.svg)

## Under the Hood: `process.env` Is a Snapshot, Taken Once, at Spawn Time

### The environment is copied from the parent when the process starts

When any process (Node or otherwise) is spawned by its parent (a shell, a script, an orchestrator), the operating system hands it a **copy** of the parent's current environment variables at that exact moment — a flat list of `KEY=value` strings. Node reads that OS-level environment once at startup and exposes it as the `process.env` object. This has two important, easy-to-miss consequences:

```js
// in a shell, BEFORE running the script:
// export API_KEY=old-value
// node script.js

console.log(process.env.API_KEY); // "old-value" — captured at spawn

// meanwhile, in ANOTHER terminal, someone runs:
// export API_KEY=new-value
// (this does absolutely nothing to the already-running Node process above)
```

1. **A running process never sees changes to the environment made *after* it started**, by anything external (another shell session, a system-wide env var change). The only way to change what a running process sees is to modify `process.env` from **inside its own JS code**, which affects only that process's in-memory copy.
2. **Writes to `process.env` inside your code do not propagate backward** to the shell/parent that launched Node, and do not retroactively affect the calling shell session after the script exits — they only exist for the lifetime of that Node process (and are visible to any child processes it spawns afterward, per the next point).

### Children inherit the environment at the moment they're spawned, not by reference

```js
const { spawn } = require("child_process");

process.env.CUSTOM_VAR = "set-before-spawn";
spawn("node", ["-e", "console.log(process.env.CUSTOM_VAR)"], { stdio: "inherit" });
// child prints "set-before-spawn" — it got a COPY at the moment spawn() was called

process.env.CUSTOM_VAR = "changed-after-spawn"; // does NOT affect the child spawned above; it's already running with its own copy
```

`child_process` (own topic) passes the current `process.env` (or an explicit `env` option override) to the child at spawn time — the child receives its own independent copy, not a live view. This is exactly the same "snapshot, not a live link" behavior between the OS and any parent-child process boundary, all the way up the chain.

### Why `.env` file loaders (dotenv-style tools) exist at all

Since the OS environment is fixed at spawn time and Node has no built-in way to read a `.env` file into `process.env` automatically (older Node versions, at least — recent Node has an experimental `--env-file` flag), tools like `dotenv` work by reading a text file at the very start of the program and writing each key into `process.env` themselves, before any other code that depends on those variables runs. It's not a special "environment file format" Node understands natively (in older versions) — it's just: read a file, split on `=`, assign into `process.env` in JS.

## Common Pitfalls

1. **Assuming `process.env.PORT` is a number** — every environment variable is a string; `"3000" + 1 === "30001"` (string concatenation, not addition) unless you explicitly convert with `Number(...)` or `parseInt(...)`.
2. **Expecting a change to the OS/shell environment to affect an already-running process** — it won't; `process.env` was copied once at spawn, and there's no live link back to the OS environment afterward.
3. **Calling `process.exit()` with pending async work you actually need to complete** (an in-flight `fs.writeFile`, a database write) — it terminates immediately, dropping unfinished I/O; let the event loop drain naturally, or explicitly await/flush critical work first.
4. **Doing async work inside a `process.on('exit', ...)` handler** and expecting it to run — `'exit'` fires synchronously in the final moments of shutdown; Promises, timers, and I/O started there will not execute.
5. **Using `uncaughtException`/`unhandledRejection` as routine error handling** instead of a last-resort safety net — application state is considered untrustworthy after an uncaught error; the conventional practice is to log the error and exit, not to swallow it and keep serving requests.

## Quick Reference

```
process.pid / .platform / .version / .cwd() → identity and environment info
process.argv                                  → [nodePath, scriptPath, ...userArgs]
process.env.KEY                                → always a STRING, or undefined if unset
process.env.KEY = "x"                           → sets it for THIS process (and future children) only
process.stdout / .stderr / .stdin                → Writable/Writable/Readable streams
process.exit(code)                                → terminates immediately, 0 = success, nonzero = failure
'exit'                                             → fires synchronously, no async work allowed
'uncaughtException' / 'unhandledRejection'          → last-resort safety nets, log-and-exit is the norm
SIGINT / SIGTERM                                     → interrupt / terminate signals, catchable for graceful shutdown
process.env is a SNAPSHOT                              → copied from the parent at spawn time, never live-synced afterward
```
