Child Processes
The node:child_process module lets a Node program spawn other programs — run a shell command, invoke an external binary like ffmpeg or git, or fork another Node script — each in its own fully isolated OS process. This is how you offload work that isn't JavaScript, isolate risky or crash-prone tasks, or use existing command-line tools. This page covers what a child process is, the four creation methods at a glance, communicating via stdio and messages, and the critical security rule about shell injection that catches developers out.
What a child process is
Parent (your Node app) Child (separate OS process)
┌─────────────────────┐ ┌──────────────────────────┐
│ spawn / exec / fork │ ───spawns──▶ │ ffmpeg / git / node ... │
│ │ │ own memory, own PID │
│ stdin ───────────▶│──── pipe ────▶│ stdin │
│ stdout ◀───────────│◀─── pipe ─────│ stdout │
│ stderr ◀───────────│◀─── pipe ─────│ stderr │
└─────────────────────┘ └──────────────────────────┘
Communicate over stdio streams (and IPC messages with fork)The four creation methods at a glance
Method | What it does | Use when |
|---|---|---|
| Launch a command, stream its output | Long-running / large output (stream it) |
| Run a command in a shell, buffer all output | Short commands with small output |
| Run a binary directly (no shell) | Running an executable safely with args |
| Spawn a Node script with an IPC channel | Parent↔child Node message passing |
Reading output from a child
import { spawn } from 'node:child_process'
// Run 'git log' and stream its output line by line:
const child = spawn('git', ['log', '--oneline', '-n', '20'])
child.stdout.on('data', (chunk) => process.stdout.write(chunk)) // stream output
child.stderr.on('data', (chunk) => console.error('err:', chunk.toString()))
child.on('close', (code) => {
console.log(`git exited with code ${code}`)
})
child.on('error', (err) => {
console.error('failed to start subprocess:', err) // e.g. command not found
})a1b2c3d Fix pagination off-by-one d4e5f6a Add Redis caching layer ... git exited with code 0
The shell-injection danger
import { exec, execFile } from 'node:child_process'
// ❌ DANGER: exec runs a SHELL — user input can inject commands:
const filename = userInput // e.g. "photo.jpg; rm -rf /"
exec(`convert ${filename} out.png`) // shell executes BOTH commands!
// ✅ SAFE: execFile passes args as a real array — no shell, no injection:
execFile('convert', [userInput, 'out.png']) // userInput is ONE argument, inert
// ✅ SAFE: spawn with an args array (shell: false is the default):
spawn('convert', [userInput, 'out.png'])When to use a child process
Invoking external tools —
ffmpeg,imagemagick,git,pandoc; reuse a mature binary instead of reimplementing it.Isolating crash-prone or risky work — a segfault or runaway in the child can't take down the parent.
CPU-bound work you want fully isolated — though worker threads are lighter for in-process compute.
Running a different language/runtime — call a Python script, a Go binary, a shell pipeline.
Sandboxing — run untrusted code with restricted permissions in its own process.