Node.js Child Processes
Node.js is single-threaded — it runs on a single CPU core. This is efficient for I/O-bound tasks (like reading files or making network requests), but it can become a bottleneck for CPU-intensive tasks (like image processing, data compression, or complex calculations) that block the event loop.
The built-in child_process module solves this by allowing Node.js to spawn new processes — separate programs that run independently on the operating system. These child processes can run system commands, execute other scripts, or perform heavy computations without blocking the main Node.js process.
When to Use Child Processes
- Running shell commands from within a Node.js application.
- Executing CPU-intensive tasks without blocking the main event loop.
- Running other scripts or programs (Python scripts, bash scripts, etc.).
- Parallel processing — distributing work across multiple processes.
The Four Main Methods
| Method | Description | Best For |
|---|---|---|
exec() | Runs a shell command, buffers all output, delivers it in a callback. | Simple shell commands with small output. |
execFile() | Runs an executable file directly without a shell. | Executing binary files or scripts. |
spawn() | Launches a new process and streams output in real-time. | Long-running commands with large or continuous output. |
fork() | Creates a new Node.js process with a built-in communication channel. | Running separate Node.js scripts with message passing. |
exec() – Running Shell Commands
exec() is the simplest method. It runs a command in the default system shell and buffers the complete output before delivering it via a callback:
const { exec } = require('child_process');
exec('ls -la', function(err, stdout, stderr) {
if (err) {
console.log("Error:", err.message);
return;
}
if (stderr) {
console.log("Stderr:", stderr);
}
console.log("Output:\n", stdout);
});
On Windows, use 'dir' instead of 'ls -la'.
Getting System Information with exec()
const { exec } = require('child_process');
exec('node --version', function(err, stdout) {
if (err) return console.log("Error:", err.message);
console.log("Node version:", stdout.trim());
});
exec('npm --version', function(err, stdout) {
if (err) return console.log("Error:", err.message);
console.log("npm version:", stdout.trim());
});
spawn() – Streaming Output in Real-Time
spawn() is more powerful than exec(). It does not buffer the output — instead, it streams data from the child process as it arrives. This is ideal for commands that produce a lot of output or run for a long time:
const { spawn } = require('child_process');
// Run 'ping' command (on Windows; use 'ping -c 4 google.com' on Linux/macOS)
const ping = spawn('ping', ['google.com', '-c', '4']);
// Stream standard output
ping.stdout.on('data', function(data) {
console.log("stdout:", data.toString());
});
// Stream error output
ping.stderr.on('data', function(data) {
console.error("stderr:", data.toString());
});
// Handle process exit
ping.on('close', function(code) {
console.log("Ping process exited with code:", code);
});
Using spawn() to Run a Python Script
// Assumes Python 3 is installed and script.py exists
const { spawn } = require('child_process');
const python = spawn('python3', ['script.py', 'arg1', 'arg2']);
python.stdout.on('data', function(data) {
console.log("Python output:", data.toString());
});
python.stderr.on('data', function(data) {
console.error("Python error:", data.toString());
});
python.on('close', function(code) {
console.log("Python process finished with code:", code);
});
fork() – Running Another Node.js Script
fork() is a special version of spawn() specifically for running Node.js scripts. The key advantage is that it establishes a built-in message-passing channel between the parent and child processes using process.send() and process.on('message').
The Child Script – worker.js
// worker.js
// Listen for messages from the parent
process.on('message', function(data) {
console.log("[Worker] Received task:", data);
// Simulate a heavy computation
let result = 0;
for (let i = 0; i <= data.limit; i++) {
result += i;
}
// Send the result back to the parent
process.send({ taskId: data.taskId, result });
});
The Parent Script – app.js
// app.js
const { fork } = require('child_process');
// Spawn a child Node.js process
const worker = fork('./worker.js');
// Send a task to the worker
worker.send({ taskId: 'task-1', limit: 1000000 });
// Receive the result from the worker
worker.on('message', function(response) {
console.log("[Parent] Task complete:", response.taskId);
console.log("[Parent] Result:", response.result);
worker.kill(); // Shut down the worker when done
});
console.log("[Parent] Task sent to worker. Continuing with other work...");
Output:
[Parent] Task sent to worker. Continuing with other work...
[Worker] Received task: { taskId: 'task-1', limit: 1000000 }
[Parent] Task complete: task-1
[Parent] Result: 500000500000
The parent process is not blocked while the worker performs the calculation. This is the key advantage of fork().
execFile() – Running Executable Files Directly
const { execFile } = require('child_process');
execFile('node', ['--version'], function(err, stdout, stderr) {
if (err) {
console.log("Error:", err.message);
return;
}
console.log("Version:", stdout.trim());
});
execFile() does not spawn a shell, making it slightly faster and safer than exec() for running executables directly.
Promisifying exec for Cleaner Code
const { exec } = require('child_process');
const util = require('util');
const execAsync = util.promisify(exec);
async function getNodeVersion() {
try {
const { stdout } = await execAsync('node --version');
console.log("Node.js version:", stdout.trim());
} catch (err) {
console.log("Error:", err.message);
}
}
getNodeVersion();
Key Points
- The
child_processmodule enables running system commands and other programs from within Node.js. exec()runs a shell command and delivers all output at once via callback — good for small, quick commands.spawn()streams output in real-time — ideal for long-running commands with large or continuous output.fork()creates a new Node.js child process with a built-in message-passing channel — ideal for offloading CPU-heavy work.execFile()runs an executable directly without a shell, making it faster and safer for simple script execution.- Child processes prevent CPU-intensive tasks from blocking Node.js's event loop.
- For even finer-grained parallelism within the same process, Worker Threads (covered next) are the modern alternative.
