Node.js Cheatsheet

Child Processes

Use this Node.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Four APIs at a Glance

APIReturnsUse When
exec(cmd, cb)ChildProcessShell command, small output, one-liner
execFile(file, args, cb)ChildProcessKnown binary, no shell, small output
spawn(cmd, args)ChildProcessStream large output, long-running process
fork(module, args)ChildProcessSpawn another Node.js module with IPC
execSync / spawnSyncBuffer/objectScripts, build steps where blocking is OK

exec — Shell Command

import { exec } from "node:child_process";
import { promisify } from "node:util";

const execAsync = promisify(exec);

// Callback style
exec("ls -la /tmp", (err, stdout, stderr) => {
  if (err) {
    console.error("Exit code:", err.code);
    console.error(stderr);
    return;
  }
  console.log(stdout);
});

// Promise style (recommended)
const { stdout, stderr } = await execAsync("git log --oneline -5");
console.log(stdout);

// With options
const { stdout } = await execAsync("npm pack --dry-run", {
  cwd:       "/path/to/project",
  env:       { ...process.env, NODE_ENV: "production" },
  timeout:   30_000,     // ms — kill after this
  maxBuffer: 10 * 1024 * 1024, // 10 MB stdout/stderr buffer
  shell:     "/bin/bash",
  encoding:  "utf8",
});

Shell injection risk: never pass untrusted user input to exec. Use execFile or spawn instead.

execFile — Binary Without Shell

import { execFile } from "node:child_process";
import { promisify } from "node:util";

const execFileAsync = promisify(execFile);

const { stdout } = await execFileAsync("ffprobe", [
  "-v", "quiet",
  "-print_format", "json",
  "-show_format",
  "video.mp4",
]);
const meta = JSON.parse(stdout);

spawn — Streaming Output

import { spawn } from "node:child_process";

const child = spawn("find", ["/usr", "-name", "*.js"], {
  cwd:   "/",
  env:   process.env,
  stdio: "pipe",      // "pipe" | "inherit" | "ignore" | Stream
});

// Stream stdout
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk) => process.stdout.write(chunk));
child.stderr.on("data", (chunk) => process.stderr.write(chunk));

child.on("close",  (code, signal) => console.log("exited", code, signal));
child.on("error",  (err) => console.error("spawn error", err));
child.on("spawn",  () => console.log("process started"));

// Send data to child's stdin
child.stdin.write("hello\n");
child.stdin.end();

// Kill
child.kill("SIGTERM");
child.kill();         // SIGTERM default

stdio Options

// "pipe"    — create pipe between parent and child (default)
// "inherit" — share parent's stdio (child output goes to terminal)
// "ignore"  — discard (redirect to /dev/null)
// Stream    — use that stream directly

spawn("node", ["server.js"], { stdio: "inherit" });   // output to terminal
spawn("cmd",  ["arg"],       { stdio: "ignore" });    // discard all
spawn("cmd",  ["arg"],       {
  stdio: [process.stdin, process.stdout, "ignore"],  // mixed
});

// Create IPC channel alongside stdio
spawn("node", ["worker.js"], { stdio: ["pipe", "pipe", "pipe", "ipc"] });

fork — IPC with Another Node.js Module

// parent.js
import { fork } from "node:child_process";

const child = fork("./worker.js", ["--mode=fast"], {
  execArgv: ["--max-old-space-size=512"],  // node flags for child
  env:      { ...process.env, WORKER: "1" },
  silent:   false,  // true = pipe child's stdio instead of inheriting
});

// Send message to child (IPC — JSON-serializable)
child.send({ type: "start", data: [1, 2, 3] });

// Receive message from child
child.on("message", (msg) => {
  console.log("from child:", msg);
});

child.on("exit", (code) => console.log("worker exited", code));

// worker.js
process.on("message", (msg) => {
  if (msg.type === "start") {
    const result = heavyWork(msg.data);
    process.send({ type: "result", result });
  }
});

Synchronous Variants

import { execSync, execFileSync, spawnSync } from "node:child_process";

// execSync — returns Buffer or string; throws on non-zero exit
const out = execSync("git rev-parse HEAD", { encoding: "utf8" }).trim();

// Silence output
execSync("npm install", { stdio: "ignore" });

// spawnSync — returns result object
const result = spawnSync("node", ["--version"], { encoding: "utf8" });
result.stdout;   // string
result.stderr;   // string
result.status;   // exit code (null if killed by signal)
result.signal;   // signal name or null
result.error;    // Error object if spawn failed
result.pid;      // child PID

ChildProcess Properties and Events

child.pid;             // child process ID
child.exitCode;        // null while running, number after exit
child.signalCode;      // signal that killed it, or null
child.killed;          // true after kill() called
child.connected;       // IPC channel open?
child.channel;         // IPC channel object (fork only)

// Events
child.on("spawn",   () => { ... });                    // process started
child.on("error",   (err) => { ... });                 // failed to start
child.on("exit",    (code, signal) => { ... });        // fires first, stdio may not be flushed
child.on("close",   (code, signal) => { ... });        // after stdio closed — use this
child.on("message", (msg, handle) => { ... });         // IPC (fork)
child.on("disconnect", () => { ... });                 // IPC channel closed

// Disconnect IPC (fork)
child.disconnect();
process.disconnect();  // inside worker

Passing Handles (Advanced IPC)

// Send a server handle to a worker (load balancing)
import net  from "node:net";
import { fork } from "node:child_process";

const worker = fork("./worker.js");
const server = net.createServer();
server.listen(3000, () => {
  worker.send("server", server);  // transfer handle
  server.close();
});

// worker.js
process.on("message", (msg, handle) => {
  if (msg === "server") {
    handle.on("connection", (socket) => {
      socket.end("handled by worker\n");
    });
  }
});

Common Patterns

// Run a shell command and get trimmed output
async function run(cmd) {
  const { stdout } = await execAsync(cmd);
  return stdout.trim();
}
const branch = await run("git branch --show-current");

// Stream a long-running process with live output
function runLive(cmd, args, opts = {}) {
  return new Promise((resolve, reject) => {
    const child = spawn(cmd, args, { stdio: "inherit", ...opts });
    child.on("close", (code) => {
      if (code === 0) resolve();
      else reject(Object.assign(new Error(`Exit ${code}`), { code }));
    });
    child.on("error", reject);
  });
}
await runLive("npm", ["run", "build"]);

// Kill a child process after a timeout
const child = spawn("long-task", []);
const timer = setTimeout(() => child.kill("SIGKILL"), 60_000);
child.on("close", () => clearTimeout(timer));

// Detached process (survives parent exit)
const daemon = spawn("node", ["daemon.js"], {
  detached: true,
  stdio:    "ignore",
});
daemon.unref();   // allow parent to exit without waiting for child