Appearance
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:
- 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.
- 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
| Function | Sync | Shell | Output |
|---|---|---|---|
spawn() | Streaming (async) | No [†] | stdio streams |
fork() | Streaming (async) | No | stdio + IPC |
execFile() | Buffered (async) | No [†] | callback |
exec() | Buffered (async) | Yes | callback |
spawnSync() | Sync | No [†] | return value |
execFileSync() | Sync | No [†] | return value |
execSync() | Sync | Yes | return value |
[†] Can opt into shell mode with { shell: true }.
Behavior differences
| Synchronicity | Errors | Buffer | Return |
|---|---|---|---|
| Streaming (async) | Events | Unbounded | ChildProcess |
| Buffered (async) | Callback | Bounded | ChildProcess |
| Sync | Throws | Bounded | Output 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()vsexec():spawnstreams output and has no buffer limit.execbuffers everything in memory (default 1 MB max). Usespawnfor long-running or high-output commands.- Shell mode security: Passing user input through a shell enables injection attacks. Prefer
spawn()orexecFile()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
closeevent fires after all stdio streams close, whileexitfires when the process exits (streams may still have buffered data).