Skip to content

Node.js child_process Functions

When to use

Use these patterns when you need to run external commands or spawn processes from Node.js. The built-in child_process module provides seven functions across two axes: synchronicity (streaming async, buffered async, sync) and shell usage (with or without a shell). Choosing the right function avoids common pitfalls around buffering, error handling, and security.

The pattern

Pick the function based on two questions:

  1. Do you need streaming output or can you wait for completion? Streaming lets you process output line-by-line as it arrives. Buffered waits for the process to finish and gives you the full output.
  2. Does the command need a shell? Pipes, redirects, globs, and environment variable expansion require a shell. Direct execution is faster and avoids shell injection risks.

Function reference

FunctionSyncShellOutput
spawn()Streaming (async)No [†]stdio streams
fork()Streaming (async)Nostdio + IPC
execFile()Buffered (async)No [†]callback
exec()Buffered (async)Yescallback
spawnSync()SyncNo [†]return value
execFileSync()SyncNo [†]return value
execSync()SyncYesreturn value

[†] Can opt into shell mode with { shell: true }.

Behavior differences

SynchronicityErrorsBufferReturn
Streaming (async)EventsUnboundedChildProcess
Buffered (async)CallbackBoundedChildProcess
SyncThrowsBoundedOutput object

Streaming output with spawn()

Use spawn() when you need to process output as it arrives or the command runs for a long time. Output streams directly to the parent process without buffering limits:

javascript
import { spawn } from "node:child_process";

const child = spawn("node", ["--test"], {
  stdio: ["pipe", "pipe", "pipe"],
  env: { ...process.env, FORCE_COLOR: "true" },
});

child.stdout.setEncoding("utf-8");
child.stdout.on("data", (chunk) => {
  process.stdout.write(chunk);
});

child.on("close", (code) => {
  console.log(`Exited with code ${code}`);
});

Buffered output with execFile()

Use execFile() when you need the full output at once and the command finishes quickly. The entire output is buffered in memory and delivered via callback:

javascript
import { execFile } from "node:child_process";

execFile("git", ["branch", "--show-current"], (err, stdout) => {
  if (err) throw err;
  console.log(stdout.trim());
});

Sync execution with spawnSync()

Use spawnSync() in scripts, CLI tools, or initialization code where blocking is acceptable. The function returns an object with the output and exit status:

javascript
import { spawnSync } from "node:child_process";

const { stdout, status } = spawnSync(
  "git", ["branch", "--show-current"],
  { encoding: "utf-8" }
);
if (status === 0) {
  console.log(stdout.trim());
}

Shell commands with exec()

Use exec() when the command needs pipes, redirects, or glob expansion. The command string is passed to the system shell for interpretation:

javascript
import { exec } from "node:child_process";

exec("ls -la | grep .js", (err, stdout) => {
  if (err) throw err;
  console.log(stdout);
});

IPC with fork()

Use fork() when you need to communicate with a Node.js child process via message passing. It creates a new V8 instance with a built-in IPC channel:

javascript
import { fork } from "node:child_process";

const worker = fork("./worker.js");
worker.send({ task: "process-data", id: 42 });
worker.on("message", (result) => {
  console.log("Worker result:", result);
});

Trade-offs

  • spawn() vs exec(): spawn streams output and has no buffer limit. exec buffers everything in memory (default 1 MB max). Use spawn for long-running or high-output commands.
  • Shell mode security: Passing user input through a shell enables injection attacks. Prefer spawn() or execFile() without { shell: true } when the command includes user-provided values.
  • Sync functions block the event loop. Acceptable during startup or in CLI tools but never in a server request handler.
  • fork() overhead: Creates a new V8 instance. Only use when you need IPC — for simple command execution, spawn() is lighter.
  • Error handling varies: Async functions deliver errors via events or callbacks. Sync functions throw. The close event fires after all stdio streams close, while exit fires when the process exits (streams may still have buffered data).