jsguides

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

ParameterTypeDefaultDescription
commandstring(required)The shell command to execute.
optionsobject{}Configuration object (see below).
callbackfunction(required)Called with (error, stdout, stderr) when complete.

Options object:

OptionTypeDefaultDescription
cwdstringprocess.cwd()Current working directory.
envobjectprocess.envEnvironment variables.
shellstring'/bin/sh'Shell to use.
timeoutnumber0Timeout in milliseconds (0 = no timeout).
maxBuffernumber1024 * 1024Max stdout/stderr size in bytes.
encodingstring'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

ParameterTypeDefaultDescription
filestring(required)The executable to run.
argsstring[][]Command-line arguments.
optionsobject{}Configuration object.
callbackfunction(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

ParameterTypeDefaultDescription
commandstring(required)The command to run.
argsstring[][]Command-line arguments.
optionsobject{}Configuration object.

Common options:

OptionTypeDefaultDescription
cwdstringprocess.cwd()Working directory.
envobjectprocess.envEnvironment variables.
stdioarray['pipe', 'pipe', 'pipe']Standard I/O configuration.
detachedbooleanfalseRun child independently of parent.
shellbooleanfalseExecute 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

ParameterTypeDefaultDescription
modulePathstring(required)Path to the Node.js module.
argsstring[][]Command-line arguments.
optionsobject{}Configuration object.

Key options:

OptionTypeDefaultDescription
execArgvstring[]process.execArgvNode.js flags to pass.
silentbooleanfalsePipe stdin/stdout to parent.
stdioarray(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.


See Also

  • Stream – Stream-based data processing
  • Process – Process module for runtime info
  • Events – Event emitter pattern