Back to Blog
Node.jschild_processProcessesSystem

Node.js child_process Module Guide

Master the Node.js child_process module for spawning and managing child processes.

B
Bootspring Team
Engineering
July 18, 2019
6 min read

The child_process module enables spawning and managing child processes. Here's how to use it effectively.

spawn()#

1import { spawn } from 'node: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('node', ['script.js'], { 20 cwd: '/path/to/dir', 21 env: { ...process.env, NODE_ENV: 'production' }, 22 stdio: 'pipe', 23});

exec()#

1import { exec } from 'node:child_process'; 2 3// Execute shell command 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 } 12 console.log(`stdout: ${stdout}`); 13}); 14 15// With options 16exec('npm install', { 17 cwd: '/path/to/project', 18 timeout: 60000, 19 maxBuffer: 1024 * 1024, // 1MB 20}, (error, stdout, stderr) => { 21 // Handle result 22}); 23 24// Promise wrapper 25import { promisify } from 'node:util'; 26const execAsync = promisify(exec); 27 28async function run() { 29 const { stdout, stderr } = await execAsync('git status'); 30 console.log(stdout); 31}

execFile()#

1import { execFile } from 'node:child_process'; 2 3// Execute file directly (no shell) 4execFile('node', ['--version'], (error, stdout, stderr) => { 5 console.log(`Node version: ${stdout.trim()}`); 6}); 7 8// More secure than exec for user input 9execFile('/usr/bin/git', ['log', '--oneline', '-10'], (error, stdout) => { 10 console.log(stdout); 11}); 12 13// With promisify 14import { promisify } from 'node:util'; 15const execFileAsync = promisify(execFile); 16 17async function getGitLog() { 18 const { stdout } = await execFileAsync('git', ['log', '--oneline', '-5']); 19 return stdout.split('\n'); 20}

fork()#

1// parent.js 2import { fork } from 'node:child_process'; 3 4const child = fork('./worker.js'); 5 6// Send message to child 7child.send({ task: 'processData', data: [1, 2, 3] }); 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(`Worker exited with code ${code}`); 16}); 17 18// worker.js 19process.on('message', (message) => { 20 if (message.task === 'processData') { 21 const result = message.data.map((x) => x * 2); 22 process.send({ result }); 23 } 24});

stdio Configuration#

1import { spawn } from 'node:child_process'; 2 3// Pipe all streams 4const child1 = spawn('ls', ['-la'], { 5 stdio: 'pipe', // Same as ['pipe', 'pipe', 'pipe'] 6}); 7 8// Inherit parent's stdio 9const child2 = spawn('npm', ['install'], { 10 stdio: 'inherit', // Output directly to terminal 11}); 12 13// Mixed configuration 14const child3 = spawn('node', ['script.js'], { 15 stdio: ['pipe', 'pipe', 'inherit'], // stdin, stdout piped; stderr inherited 16}); 17 18// Ignore streams 19const child4 = spawn('background-process', [], { 20 stdio: 'ignore', 21 detached: true, 22}); 23child4.unref(); // Allow parent to exit 24 25// File output 26import fs from 'node:fs'; 27const out = fs.openSync('./out.log', 'a'); 28const err = fs.openSync('./err.log', 'a'); 29 30const child5 = spawn('node', ['server.js'], { 31 stdio: ['ignore', out, err], 32 detached: true, 33}); 34child5.unref();

Stream Processing#

1import { spawn } from 'node:child_process'; 2 3// Pipe between processes 4const grep = spawn('grep', ['error']); 5const cat = spawn('cat', ['logfile.txt']); 6 7cat.stdout.pipe(grep.stdin); 8 9grep.stdout.on('data', (data) => { 10 console.log(`Found: ${data}`); 11}); 12 13// Process output line by line 14import readline from 'node:readline'; 15 16const tail = spawn('tail', ['-f', 'app.log']); 17 18const rl = readline.createInterface({ 19 input: tail.stdout, 20}); 21 22rl.on('line', (line) => { 23 console.log('Log line:', line); 24 if (line.includes('ERROR')) { 25 // Handle error 26 } 27});

Process Pool#

1import { fork } from 'node:child_process'; 2import os from 'node:os'; 3 4class ProcessPool { 5 constructor(workerScript, size = os.cpus().length) { 6 this.workerScript = workerScript; 7 this.size = size; 8 this.workers = []; 9 this.available = []; 10 this.queue = []; 11 12 this.init(); 13 } 14 15 init() { 16 for (let i = 0; i < this.size; i++) { 17 this.addWorker(); 18 } 19 } 20 21 addWorker() { 22 const worker = fork(this.workerScript); 23 24 worker.on('message', (result) => { 25 const callback = worker.currentCallback; 26 worker.currentCallback = null; 27 this.available.push(worker); 28 this.processQueue(); 29 callback(null, result); 30 }); 31 32 worker.on('error', (error) => { 33 if (worker.currentCallback) { 34 worker.currentCallback(error); 35 } 36 }); 37 38 this.workers.push(worker); 39 this.available.push(worker); 40 } 41 42 execute(task) { 43 return new Promise((resolve, reject) => { 44 this.queue.push({ task, resolve, reject }); 45 this.processQueue(); 46 }); 47 } 48 49 processQueue() { 50 if (this.queue.length === 0 || this.available.length === 0) { 51 return; 52 } 53 54 const worker = this.available.pop(); 55 const { task, resolve, reject } = this.queue.shift(); 56 57 worker.currentCallback = (err, result) => { 58 if (err) reject(err); 59 else resolve(result); 60 }; 61 62 worker.send(task); 63 } 64 65 shutdown() { 66 for (const worker of this.workers) { 67 worker.kill(); 68 } 69 } 70} 71 72// Usage 73const pool = new ProcessPool('./worker.js', 4); 74 75const results = await Promise.all([ 76 pool.execute({ type: 'compute', data: 1 }), 77 pool.execute({ type: 'compute', data: 2 }), 78 pool.execute({ type: 'compute', data: 3 }), 79]); 80 81pool.shutdown();

Error Handling#

1import { spawn, exec } from 'node:child_process'; 2 3// spawn error handling 4const child = spawn('nonexistent-command'); 5 6child.on('error', (err) => { 7 console.error('Failed to start subprocess:', err.message); 8}); 9 10child.on('exit', (code, signal) => { 11 if (signal) { 12 console.log(`Process killed by signal: ${signal}`); 13 } else if (code !== 0) { 14 console.log(`Process exited with code: ${code}`); 15 } 16}); 17 18// exec with timeout 19exec('sleep 10', { timeout: 1000 }, (error, stdout, stderr) => { 20 if (error) { 21 if (error.killed) { 22 console.log('Process was killed due to timeout'); 23 } else { 24 console.error('Execution error:', error.message); 25 } 26 } 27}); 28 29// Graceful shutdown 30function gracefulShutdown(child) { 31 return new Promise((resolve) => { 32 child.on('exit', resolve); 33 34 child.kill('SIGTERM'); 35 36 setTimeout(() => { 37 if (!child.killed) { 38 child.kill('SIGKILL'); 39 } 40 }, 5000); 41 }); 42}

Shell Commands#

1import { spawn, exec } from 'node:child_process'; 2 3// exec runs in shell by default 4exec('echo $HOME && ls | head -5', (error, stdout) => { 5 console.log(stdout); 6}); 7 8// spawn with shell 9const child = spawn('echo $HOME && ls | head -5', { 10 shell: true, 11}); 12 13// Windows shell 14const winChild = spawn('dir', { 15 shell: true, // Uses cmd.exe on Windows 16}); 17 18// Cross-platform 19const isWindows = process.platform === 'win32'; 20const child = spawn(isWindows ? 'dir' : 'ls', { 21 shell: true, 22});

execSync and spawnSync#

1import { execSync, spawnSync } from 'node:child_process'; 2 3// Synchronous execution 4try { 5 const result = execSync('git rev-parse HEAD', { 6 encoding: 'utf8', 7 }); 8 console.log('Git SHA:', result.trim()); 9} catch (error) { 10 console.error('Command failed:', error.message); 11} 12 13// spawnSync 14const { status, stdout, stderr } = spawnSync('npm', ['--version'], { 15 encoding: 'utf8', 16}); 17 18if (status === 0) { 19 console.log('npm version:', stdout.trim()); 20} else { 21 console.error('Error:', stderr); 22} 23 24// Use for scripts, avoid in servers 25// Blocks event loop!

Best Practices#

Method Selection: ✓ spawn for streams/long-running ✓ exec for shell commands ✓ execFile for security ✓ fork for Node.js IPC Error Handling: ✓ Listen for 'error' event ✓ Check exit codes ✓ Handle signals ✓ Implement timeouts Performance: ✓ Use process pools ✓ Avoid sync methods in servers ✓ Stream large outputs ✓ Limit concurrent processes Security: ✓ Use execFile for user input ✓ Validate command arguments ✓ Set appropriate timeouts ✓ Limit maxBuffer Avoid: ✗ Shell injection with exec ✗ Sync methods in async code ✗ Ignoring errors ✗ Orphan processes

Conclusion#

The child_process module provides multiple ways to spawn processes: spawn for streaming, exec for shell commands, execFile for direct execution, and fork for Node.js IPC. Choose the appropriate method based on your needs, handle errors properly, and be mindful of security when dealing with user input. For CPU-intensive tasks, consider process pools to maximize throughput while managing resources effectively.

Share this article

Help spread the word about Bootspring