Build tools, CLIs and backends constantly shell out to git, ffmpeg, image tools or other Node scripts.
node:child_process starts other programs. Pick the function by what you need:
- `execFile(file, args)` — run a program and buffer its output; great for short commands like
git rev-parse HEAD. Output beyondmaxBuffer(1 MB) is an error. - `spawn(cmd, args)` — returns a
ChildProcesswithstdout/stderr/stdinstreams; use it for long-running or chatty processes and large output.stdio: "inherit"connects the child directly to your terminal. - `exec(commandString)` — runs a string through a shell (
/bin/shorcmd.exe). Convenient for pipes and globs, dangerous with user input. - `fork(modulePath)` — spawns a new Node process with an IPC channel, so parent and child exchange objects with
send()/on("message").
The security rule: never build a shell command from untrusted input. exec(\convert ${filename} out.png\) with a filename like x; rm -rf ~ runs both commands. execFile and spawn without shell: true pass arguments directly to the program, so no shell parsing happens.
Exit status arrives on the 'close' event (after stdio streams finish) as code, or signal if killed. A non-zero code is not an exception with spawn — check it yourself. The promisified execFile rejects on non-zero exit, with stdout/stderr attached to the error.
Control: child.kill("SIGTERM"), the timeout and signal (AbortSignal) options, cwd and env for the child's environment. The *Sync variants block the event loop; fine in build scripts, not in servers.
Each child is a full OS process — tens of milliseconds to start, separate memory. For heavy JavaScript work prefer worker threads; use child processes for other programs or isolation.
Example
import { execFile, spawn } from "node:child_process";
import { promisify } from "node:util";
import { once } from "node:events";
const run = promisify(execFile);
// Short command, arguments passed safely (no shell)
const { stdout } = await run("node", ["-p", "process.versions.v8"]);
console.log("v8", stdout.trim());
// Long-running command with streamed output
const child = spawn("node", ["-e", "for (let i = 1; i <= 3; i++) console.log('line', i)"], {
signal: AbortSignal.timeout(5000),
});
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk) => process.stdout.write(`[child] ${chunk}`));
child.on("error", (err) => console.error("failed to start or aborted:", err.message));
const [code] = await once(child, "close");
console.log("exit code", code); // 0
// DON'T: exec(`grep ${userInput} file.txt`) — shell injection
// DO: execFile("grep", [userInput, "file.txt"])Remember
execFilefor short commands,spawnfor streaming,forkfor Node-to-Node IPC- Never interpolate user input into
execorshell: truecommands - Check the exit code; handle the
'error'event
Watch out for
- Using
execwith string interpolation of user-controlled values. - Buffering huge output with exec/execFile and hitting maxBuffer.