Child processes allow Node.js to run external commands and parallelize work. Here's how to use them effectively.
Overview of Methods#
1const { exec, execFile, spawn, fork } = require('child_process');
2
3// exec: Run command in shell, buffer output
4// execFile: Run executable directly, buffer output
5// spawn: Run command, stream output
6// fork: Special spawn for Node.js scripts with IPCUsing exec#
1const { exec } = require('child_process');
2
3// Basic usage
4exec('ls -la', (error, stdout, stderr) => {
5 if (error) {
6 console.error(`Error: ${error.message}`);
7 return;
8 }
9 if (stderr) {
10 console.error(`stderr: ${stderr}`);
11 return;
12 }
13 console.log(`stdout: ${stdout}`);
14});
15
16// With options
17exec('cat package.json', {
18 cwd: '/path/to/project',
19 encoding: 'utf8',
20 maxBuffer: 1024 * 1024, // 1MB
21 timeout: 5000,
22 env: { ...process.env, NODE_ENV: 'production' },
23}, (error, stdout, stderr) => {
24 console.log(stdout);
25});
26
27// Promise wrapper
28const { promisify } = require('util');
29const execAsync = promisify(exec);
30
31async function runCommand(cmd) {
32 try {
33 const { stdout, stderr } = await execAsync(cmd);
34 return stdout.trim();
35 } catch (error) {
36 throw new Error(`Command failed: ${error.message}`);
37 }
38}
39
40// Usage
41const gitBranch = await runCommand('git branch --show-current');
42console.log(`Current branch: ${gitBranch}`);Using execFile#
1const { execFile } = require('child_process');
2
3// More secure - doesn't use shell
4execFile('node', ['--version'], (error, stdout, stderr) => {
5 if (error) {
6 console.error(error);
7 return;
8 }
9 console.log(`Node version: ${stdout}`);
10});
11
12// Run a script
13execFile('./script.sh', ['arg1', 'arg2'], {
14 cwd: __dirname,
15}, (error, stdout, stderr) => {
16 console.log(stdout);
17});
18
19// Promise version
20const { promisify } = require('util');
21const execFileAsync = promisify(execFile);
22
23async function runScript() {
24 const { stdout } = await execFileAsync('python3', ['script.py', '--flag']);
25 return JSON.parse(stdout);
26}Using spawn#
1const { spawn } = require('child_process');
2
3// Basic spawn
4const ls = spawn('ls', ['-la']);
5
6ls.stdout.on('data', (data) => {
7 console.log(`stdout: ${data}`);
8});
9
10ls.stderr.on('data', (data) => {
11 console.error(`stderr: ${data}`);
12});
13
14ls.on('close', (code) => {
15 console.log(`Process exited with code ${code}`);
16});
17
18// With options
19const child = spawn('npm', ['install'], {
20 cwd: '/path/to/project',
21 env: { ...process.env, CI: 'true' },
22 stdio: 'inherit', // Inherit parent's stdio
23});
24
25// Pipe streams
26const grep = spawn('grep', ['pattern']);
27const cat = spawn('cat', ['file.txt']);
28
29cat.stdout.pipe(grep.stdin);
30
31grep.stdout.on('data', (data) => {
32 console.log(`Found: ${data}`);
33});
34
35// Detached process
36const child = spawn('long-running-process', [], {
37 detached: true,
38 stdio: 'ignore',
39});
40
41child.unref(); // Allow parent to exit
42
43// Shell command
44const child = spawn('echo "Hello" && echo "World"', {
45 shell: true,
46});Using fork#
1// parent.js
2const { fork } = require('child_process');
3
4const child = fork('worker.js');
5
6// Send message to child
7child.send({ type: 'START', data: [1, 2, 3, 4, 5] });
8
9// Receive messages from child
10child.on('message', (message) => {
11 console.log('Result from child:', message);
12});
13
14child.on('exit', (code) => {
15 console.log(`Child exited with code ${code}`);
16});
17
18// worker.js
19process.on('message', (message) => {
20 if (message.type === 'START') {
21 const result = message.data.reduce((a, b) => a + b, 0);
22 process.send({ type: 'RESULT', data: result });
23 }
24});
25
26// With module path
27const worker = fork('./worker.js', ['--arg1'], {
28 cwd: __dirname,
29 env: { ...process.env, WORKER_ID: '1' },
30 execArgv: ['--max-old-space-size=4096'],
31});Process Pool#
1const { fork } = require('child_process');
2const os = require('os');
3
4class ProcessPool {
5 constructor(workerPath, poolSize = os.cpus().length) {
6 this.workerPath = workerPath;
7 this.poolSize = poolSize;
8 this.workers = [];
9 this.taskQueue = [];
10 this.activeWorkers = new Map();
11
12 this.initialize();
13 }
14
15 initialize() {
16 for (let i = 0; i < this.poolSize; i++) {
17 this.createWorker();
18 }
19 }
20
21 createWorker() {
22 const worker = fork(this.workerPath);
23
24 worker.on('message', (result) => {
25 const { resolve, reject } = this.activeWorkers.get(worker);
26
27 if (result.error) {
28 reject(new Error(result.error));
29 } else {
30 resolve(result.data);
31 }
32
33 this.activeWorkers.delete(worker);
34 this.workers.push(worker);
35 this.processQueue();
36 });
37
38 worker.on('error', (error) => {
39 const task = this.activeWorkers.get(worker);
40 if (task) {
41 task.reject(error);
42 this.activeWorkers.delete(worker);
43 }
44 this.createWorker(); // Replace dead worker
45 });
46
47 this.workers.push(worker);
48 }
49
50 async execute(data) {
51 return new Promise((resolve, reject) => {
52 this.taskQueue.push({ data, resolve, reject });
53 this.processQueue();
54 });
55 }
56
57 processQueue() {
58 while (this.workers.length > 0 && this.taskQueue.length > 0) {
59 const worker = this.workers.pop();
60 const task = this.taskQueue.shift();
61
62 this.activeWorkers.set(worker, task);
63 worker.send(task.data);
64 }
65 }
66
67 shutdown() {
68 for (const worker of this.workers) {
69 worker.kill();
70 }
71 for (const worker of this.activeWorkers.keys()) {
72 worker.kill();
73 }
74 }
75}
76
77// worker.js
78process.on('message', async (data) => {
79 try {
80 const result = await processData(data);
81 process.send({ data: result });
82 } catch (error) {
83 process.send({ error: error.message });
84 }
85});
86
87// Usage
88const pool = new ProcessPool('./worker.js', 4);
89
90const results = await Promise.all([
91 pool.execute({ task: 'compute', value: 100 }),
92 pool.execute({ task: 'compute', value: 200 }),
93 pool.execute({ task: 'compute', value: 300 }),
94]);Streaming Large Data#
1const { spawn } = require('child_process');
2const fs = require('fs');
3
4// Process large file with external tool
5function processLargeFile(inputPath, outputPath) {
6 return new Promise((resolve, reject) => {
7 const input = fs.createReadStream(inputPath);
8 const output = fs.createWriteStream(outputPath);
9
10 const gzip = spawn('gzip', ['-c']);
11
12 input.pipe(gzip.stdin);
13 gzip.stdout.pipe(output);
14
15 gzip.on('close', (code) => {
16 if (code === 0) {
17 resolve();
18 } else {
19 reject(new Error(`gzip exited with code ${code}`));
20 }
21 });
22
23 gzip.on('error', reject);
24 });
25}
26
27// FFmpeg example
28function convertVideo(input, output) {
29 return new Promise((resolve, reject) => {
30 const ffmpeg = spawn('ffmpeg', [
31 '-i', input,
32 '-c:v', 'libx264',
33 '-preset', 'fast',
34 '-c:a', 'aac',
35 output,
36 ]);
37
38 ffmpeg.stderr.on('data', (data) => {
39 // FFmpeg outputs progress to stderr
40 const progress = parseProgress(data.toString());
41 if (progress) {
42 console.log(`Progress: ${progress}%`);
43 }
44 });
45
46 ffmpeg.on('close', (code) => {
47 code === 0 ? resolve() : reject(new Error('Conversion failed'));
48 });
49 });
50}Error Handling#
1const { spawn, exec } = require('child_process');
2
3// Comprehensive error handling
4function runCommand(command, args = []) {
5 return new Promise((resolve, reject) => {
6 const child = spawn(command, args);
7
8 let stdout = '';
9 let stderr = '';
10
11 child.stdout.on('data', (data) => {
12 stdout += data;
13 });
14
15 child.stderr.on('data', (data) => {
16 stderr += data;
17 });
18
19 child.on('error', (error) => {
20 reject(new Error(`Failed to start: ${error.message}`));
21 });
22
23 child.on('close', (code, signal) => {
24 if (signal) {
25 reject(new Error(`Process killed with signal ${signal}`));
26 } else if (code !== 0) {
27 const error = new Error(`Process exited with code ${code}`);
28 error.stdout = stdout;
29 error.stderr = stderr;
30 reject(error);
31 } else {
32 resolve({ stdout, stderr });
33 }
34 });
35 });
36}
37
38// Timeout handling
39function runWithTimeout(command, timeout = 30000) {
40 return new Promise((resolve, reject) => {
41 const child = exec(command, { timeout });
42
43 child.on('error', reject);
44
45 child.on('close', (code, signal) => {
46 if (signal === 'SIGTERM') {
47 reject(new Error('Process timed out'));
48 } else if (code === 0) {
49 resolve();
50 } else {
51 reject(new Error(`Exit code: ${code}`));
52 }
53 });
54 });
55}IPC Communication#
1// parent.js
2const { fork } = require('child_process');
3
4const child = fork('worker.js', [], {
5 serialization: 'advanced', // Support more data types
6});
7
8// Send various data types
9child.send({
10 buffer: Buffer.from('hello'),
11 date: new Date(),
12 map: new Map([['key', 'value']]),
13});
14
15// Bidirectional communication
16child.on('message', (msg) => {
17 if (msg.type === 'REQUEST_DATA') {
18 child.send({ type: 'DATA', payload: getData() });
19 }
20});
21
22// Send file descriptor
23const net = require('net');
24const server = net.createServer();
25
26server.listen(0, () => {
27 child.send('server', server); // Pass server handle
28});
29
30// worker.js
31process.on('message', (msg, handle) => {
32 if (handle) {
33 // Received a socket or server
34 handle.on('connection', (socket) => {
35 // Handle connection in child process
36 });
37 }
38});Cluster Module Integration#
1const cluster = require('cluster');
2const http = require('http');
3const os = require('os');
4
5if (cluster.isPrimary) {
6 const numCPUs = os.cpus().length;
7
8 console.log(`Primary ${process.pid} is running`);
9
10 // Fork workers
11 for (let i = 0; i < numCPUs; i++) {
12 cluster.fork();
13 }
14
15 cluster.on('exit', (worker, code, signal) => {
16 console.log(`Worker ${worker.process.pid} died`);
17 cluster.fork(); // Replace dead worker
18 });
19
20 // Message from workers
21 cluster.on('message', (worker, message) => {
22 console.log(`Message from worker ${worker.id}:`, message);
23 });
24
25} else {
26 // Workers share TCP connection
27 http.createServer((req, res) => {
28 res.writeHead(200);
29 res.end(`Hello from worker ${cluster.worker.id}\n`);
30 }).listen(8000);
31
32 console.log(`Worker ${process.pid} started`);
33
34 // Send message to primary
35 process.send({ type: 'ready', pid: process.pid });
36}Best Practices#
Method Selection:
✓ exec: Simple commands, need shell features
✓ execFile: Known executables, security matters
✓ spawn: Large output, streaming needed
✓ fork: Node.js scripts with IPC
Security:
✓ Prefer execFile over exec
✓ Validate user input
✓ Use shell: false when possible
✓ Set appropriate cwd and env
Performance:
✓ Use process pools for heavy work
✓ Stream large data instead of buffering
✓ Limit concurrent processes
✓ Handle cleanup properly
Error Handling:
✓ Listen to 'error' events
✓ Check exit codes and signals
✓ Handle timeouts
✓ Log stderr appropriately
Conclusion#
Child processes enable Node.js to run external commands and parallelize CPU-intensive work. Use exec for simple shell commands, spawn for streaming, and fork for Node.js worker processes with IPC. Implement process pools for heavy workloads and always handle errors appropriately.