child_process module
The child_process module is a core Node.js module for spawning child processes, executing shell commands, and running external programs. Need to call a Python script? Run a build tool? The module handles it. It supports both asynchronous APIs — exec, execFile, spawn, fork — and their synchronous counterparts, giving you flexibility for any task that reaches beyond the Node.js runtime.
Syntax
const { exec, execFile, spawn, fork } = require('child_process'); // CommonJS
import { exec, execFile, spawn, fork } from 'child_process'; // ES modules
exec()
Executes a shell command and buffers the output. The command string is passed to the system shell (/bin/sh on Unix, cmd.exe on Windows), which means you can use shell features like pipes, environment variable expansion, and globbing. This makes exec() convenient for quick one-liners, but output is held in memory until the command finishes — avoid it for commands that produce large amounts of data.
Syntax
exec(command[, options], callback)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
command | string | (required) | The shell command to execute. |
options | object | {} | Configuration object (see below). |
callback | function | (required) | Called with (error, stdout, stderr) when complete. |
Options object:
| Option | Type | Default | Description |
|---|---|---|---|
cwd | string | process.cwd() | Current working directory. |
env | object | process.env | Environment variables. |
shell | string | '/bin/sh' | Shell to use. |
timeout | number | 0 | Timeout in milliseconds (0 = no timeout). |
maxBuffer | number | 1024 * 1024 | Max stdout/stderr size in bytes. |
encoding | string | 'utf8' | Output encoding. |
Example
const { exec } = require('child_process');
exec('ls -la', { cwd: '/tmp' }, (error, stdout, stderr) => {
if (error) {
console.error('Execution error:', error.message);
return;
}
console.log('Output:', stdout);
});
Return Value
Returns a ChildProcess instance. The callback receives stdout and stderr as strings (or buffers if encoding is ‘buffer’). Because exec() pipes the command through a shell, it supports pipes, redirects, and variable expansion — but that also means you should never pass unsanitised user input directly into the command string. For user-supplied arguments, prefer execFile() to avoid shell injection.
execFile()
Executes a file directly without spawning a shell. This is safer than exec() when you do not need shell features because it avoids shell injection vulnerabilities. The first argument should be the executable path or filename that will be searched in PATH.
Syntax
execFile(file[, args][, options], callback)
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
file | string | (required) | The executable to run. |
args | string[] | [] | Command-line arguments. |
options | object | {} | Configuration object. |
callback | function | (required) | Called with (error, stdout, stderr). |
The callback receives error, stdout, and stderr — the same signature as exec(). The key difference is that arguments are passed as an array, so no shell interprets them.
Example
const { execFile } = require('child_process');
execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
console.error('Failed to run node:', error);
return;
}
console.log('Node version:', stdout.trim());
});
Return Value
Returns a ChildProcess instance. Unlike exec(), no shell is involved, so features like pipes or globbing will not work.
spawn()
Spawns a new child process without buffering output. Data is streamed via event listeners, making it ideal for long-running processes or when you need to process output incrementally. This is the most flexible method in the module.
Syntax
spawn(command[, args][, options])
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
command | string | (required) | The command to run. |
args | string[] | [] | Command-line arguments. |
options | object | {} | Configuration object. |
Common options:
| Option | Type | Default | Description |
|---|---|---|---|
cwd | string | process.cwd() | Working directory. |
env | object | process.env | Environment variables. |
stdio | array | ['pipe', 'pipe', 'pipe'] | Standard I/O configuration. |
detached | boolean | false | Run child independently of parent. |
shell | boolean | false | Execute through shell. |
Example
const { spawn } = require('child_process');
const child = spawn('grep', ['-r', 'needle', './src']);
child.stdout.on('data', (data) => {
console.log('stdout:', data.toString());
});
child.stderr.on('data', (data) => {
console.error('stderr:', data.toString());
});
child.on('close', (code) => {
console.log('Process exited with code:', code);
});
Return Value
Returns a ChildProcess instance with stdout, stderr, and stdin as streams. You listen to ‘data’, ‘close’, and ‘error’ events.
fork()
A special case of spawn() specifically for spawning Node.js modules. The forked process runs as a separate Node.js instance and includes a built-in communication channel (IPC) for message passing between parent and child.
Syntax
fork(modulePath[, args][, options])
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
modulePath | string | (required) | Path to the Node.js module. |
args | string[] | [] | Command-line arguments. |
options | object | {} | Configuration object. |
Key options:
| Option | Type | Default | Description |
|---|---|---|---|
execArgv | string[] | process.execArgv | Node.js flags to pass. |
silent | boolean | false | Pipe stdin/stdout to parent. |
stdio | array | (see docs) | Standard I/O configuration. |
The forked child runs as a completely separate V8 instance with its own event loop. Communication happens through a serialised message channel — the parent calls child.send() and listens for 'message' events, while the child does the same on process.
Example
// parent.js
const { fork } = require('child_process');
const child = fork('./child.js');
child.on('message', (msg) => {
console.log('Received from child:', msg);
});
child.send({ action: 'start' });
// child.js
process.on('message', (msg) => {
console.log('Received from parent:', msg);
process.send({ status: 'running' });
});
Return Value
Returns a ChildProcess with an additional send() method and ‘message’ event for IPC communication. The fork() method is the only API in the module that sets up IPC automatically — none of exec, execFile, or spawn create a message channel unless you configure stdio manually.
Synchronous Variants
The synchronous variants block the event loop until the child process completes. Use them for simple scripts where async is not necessary.
execSync()
const { execSync } = require('child_process');
const output = execSync('ls -la', { encoding: 'utf8' });
console.log(output);
execSync runs a shell command and returns its stdout as a buffer or string. If you need to run a specific executable without the overhead of a shell, use execFileSync instead — it accepts the program path and arguments as separate parameters, which avoids shell injection.
execFileSync()
const { execFileSync } = require('child_process');
const version = execFileSync('node', ['--version'], { encoding: 'utf8' });
console.log(version);
Both execSync and execFileSync buffer the entire output before returning — convenient for short-lived commands, but they block the event loop for the duration. For commands that produce large output or run for unpredictable lengths, spawnSync gives you finer control through its result object, which includes separate stdout, stderr, and status fields instead of throwing on non-zero exits.
spawnSync()
const { spawnSync } = require('child_process');
const result = spawnSync('node', ['-e', 'console.log("hello")']);
console.log('stdout:', result.stdout.toString());
console.log('stderr:', result.stderr.toString());
console.log('status:', result.status);
All synchronous variants return a Buffer or string (based on encoding option) and throw on error. Check the status property for exit codes. For real-world scripts, the synchronous APIs are rarely the right choice — they block the event loop and cannot stream output. The following patterns use the async methods, which are what you will reach for in servers, CLIs, and automation tasks that need to stay responsive.
Common Patterns
Running a Python script and capturing output
const { execFile } = require('child_process');
execFile('python3', ['script.py', '--input', 'data.txt'],
{ encoding: 'utf8' },
(error, stdout, stderr) => {
if (error) {
console.error('Script failed:', error.message);
return;
}
console.log('Result:', stdout);
}
);
Calling an external script with arguments is a common task, but some processes need to stay alive for the lifetime of your application — a web server, a background worker, or a daemon. For those cases, use spawn to launch the process and listen to its events rather than waiting for a single completion callback.
Long-running process with streaming
const { spawn } = require('child_process');
const nginx = spawn('nginx');
nginx.on('error', (err) => {
console.error('Failed to start nginx:', err);
});
process.on('SIGTERM', () => {
nginx.kill();
process.exit(0);
});
A long-running process like nginx runs until you explicitly stop it, and you listen for errors to detect startup failures. For commands that should complete within a known timeframe, the opposite pattern applies: you set a timeout and check whether the process was killed before it could finish, which prevents hung commands from blocking your application indefinitely.
Handling timeouts
const { exec } = require('child_process');
const child = exec('sleep 10', { timeout: 2000 }, (error) => {
if (error && error.killed) {
console.log('Process timed out and was killed');
}
});
The timeout option kills the process after the specified duration but does not throw — you check error.killed to distinguish a timeout from a genuine execution error. This pattern is useful for any command that might hang, such as network requests through shell utilities or third-party scripts with no built-in deadline.