Child processes enable parallel execution and system command integration. Here's how to use them effectively.
spawn vs exec vs fork#
1const { spawn, exec, execFile, fork } = require('child_process');
2
3// spawn - streaming output, best for long-running processes
4const ls = spawn('ls', ['-la', '/tmp']);
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(`child process exited with code ${code}`);
16});
17
18// exec - buffered output, runs in shell
19exec('ls -la /tmp', (error, stdout, stderr) => {
20 if (error) {
21 console.error(`exec error: ${error}`);
22 return;
23 }
24 console.log(`stdout: ${stdout}`);
25 console.error(`stderr: ${stderr}`);
26});
27
28// execFile - like exec but doesn't spawn shell
29execFile('node', ['--version'], (error, stdout) => {
30 if (error) throw error;
31 console.log(`Node version: ${stdout}`);
32});
33
34// fork - special spawn for Node.js scripts with IPC
35const child = fork('./worker.js');
36child.send({ type: 'start', data: [1, 2, 3] });
37child.on('message', (msg) => {
38 console.log('Message from child:', msg);
39});Promisified Execution#
1const { exec, spawn } = require('child_process');
2const { promisify } = require('util');
3
4const execPromise = promisify(exec);
5
6async function runCommand(cmd) {
7 try {
8 const { stdout, stderr } = await execPromise(cmd);
9 return { stdout, stderr };
10 } catch (error) {
11 throw new Error(`Command failed: ${error.message}`);
12 }
13}
14
15// Usage
16async function main() {
17 const result = await runCommand('ls -la');
18 console.log(result.stdout);
19}
20
21// Spawn with promise
22function spawnPromise(command, args, options = {}) {
23 return new Promise((resolve, reject) => {
24 const child = spawn(command, args, options);
25 let stdout = '';
26 let stderr = '';
27
28 child.stdout?.on('data', (data) => {
29 stdout += data;
30 });
31
32 child.stderr?.on('data', (data) => {
33 stderr += data;
34 });
35
36 child.on('close', (code) => {
37 if (code === 0) {
38 resolve({ stdout, stderr, code });
39 } else {
40 reject(new Error(`Process exited with code ${code}: ${stderr}`));
41 }
42 });
43
44 child.on('error', reject);
45 });
46}Streaming Data#
1const { spawn } = require('child_process');
2const fs = require('fs');
3
4// Pipe between processes
5const grep = spawn('grep', ['error']);
6const cat = spawn('cat', ['logs.txt']);
7
8cat.stdout.pipe(grep.stdin);
9
10grep.stdout.on('data', (data) => {
11 console.log(`Found: ${data}`);
12});
13
14// Write to stdin
15const sort = spawn('sort');
16
17sort.stdout.on('data', (data) => {
18 console.log(`Sorted:\n${data}`);
19});
20
21sort.stdin.write('banana\n');
22sort.stdin.write('apple\n');
23sort.stdin.write('cherry\n');
24sort.stdin.end();
25
26// Stream to file
27const ls = spawn('ls', ['-la']);
28const output = fs.createWriteStream('listing.txt');
29
30ls.stdout.pipe(output);
31
32ls.on('close', (code) => {
33 console.log(`Listing saved, exit code: ${code}`);
34});IPC Communication (fork)#
1// parent.js
2const { fork } = require('child_process');
3
4const workers = [];
5const numWorkers = 4;
6
7// Create worker pool
8for (let i = 0; i < numWorkers; i++) {
9 const worker = fork('./worker.js');
10
11 worker.on('message', (msg) => {
12 console.log(`Worker ${i} completed:`, msg);
13 });
14
15 worker.on('exit', (code) => {
16 console.log(`Worker ${i} exited with code ${code}`);
17 });
18
19 workers.push(worker);
20}
21
22// Distribute work
23const tasks = [1, 2, 3, 4, 5, 6, 7, 8];
24tasks.forEach((task, index) => {
25 const worker = workers[index % numWorkers];
26 worker.send({ type: 'task', data: task });
27});
28
29// Shutdown workers
30setTimeout(() => {
31 workers.forEach(worker => {
32 worker.send({ type: 'shutdown' });
33 });
34}, 5000);
35
36// worker.js
37process.on('message', (msg) => {
38 switch (msg.type) {
39 case 'task':
40 const result = msg.data * 2; // Do work
41 process.send({ type: 'result', data: result });
42 break;
43 case 'shutdown':
44 process.exit(0);
45 break;
46 }
47});Detached Processes#
1const { spawn } = require('child_process');
2const fs = require('fs');
3
4// Run process that survives parent exit
5const out = fs.openSync('./daemon.log', 'a');
6const err = fs.openSync('./daemon.log', 'a');
7
8const daemon = spawn('node', ['daemon.js'], {
9 detached: true,
10 stdio: ['ignore', out, err],
11});
12
13// Allow parent to exit independently
14daemon.unref();
15
16console.log(`Daemon started with PID: ${daemon.pid}`);
17process.exit(0);
18
19// daemon.js
20setInterval(() => {
21 console.log(`Daemon running at ${new Date().toISOString()}`);
22}, 1000);Shell Commands#
1const { exec, spawn } = require('child_process');
2
3// Complex shell commands with exec
4exec('cat *.txt | grep error | wc -l', { shell: '/bin/bash' }, (error, stdout) => {
5 console.log(`Error count: ${stdout.trim()}`);
6});
7
8// Shell with spawn
9const shell = spawn('bash', ['-c', 'for i in 1 2 3; do echo $i; sleep 1; done']);
10
11shell.stdout.on('data', (data) => {
12 console.log(data.toString());
13});
14
15// Environment variables
16exec('echo $MY_VAR', {
17 env: { ...process.env, MY_VAR: 'custom_value' }
18}, (error, stdout) => {
19 console.log(stdout); // custom_value
20});
21
22// Working directory
23exec('pwd', { cwd: '/tmp' }, (error, stdout) => {
24 console.log(stdout); // /tmp
25});Timeout and Kill#
1const { exec, spawn } = require('child_process');
2
3// Timeout with exec
4exec('sleep 10', { timeout: 2000 }, (error, stdout, stderr) => {
5 if (error) {
6 console.log('Process timed out');
7 }
8});
9
10// Manual timeout with spawn
11const longProcess = spawn('sleep', ['10']);
12
13const timeout = setTimeout(() => {
14 longProcess.kill('SIGTERM');
15 console.log('Process killed due to timeout');
16}, 2000);
17
18longProcess.on('close', () => {
19 clearTimeout(timeout);
20});
21
22// Kill signals
23const proc = spawn('node', ['server.js']);
24
25// Graceful shutdown
26proc.kill('SIGTERM');
27
28// Force kill
29setTimeout(() => {
30 if (!proc.killed) {
31 proc.kill('SIGKILL');
32 }
33}, 5000);
34
35// Handle in child process
36process.on('SIGTERM', () => {
37 console.log('Received SIGTERM, cleaning up...');
38 // Cleanup
39 process.exit(0);
40});Worker Pool Pattern#
1const { fork } = require('child_process');
2const os = require('os');
3
4class WorkerPool {
5 constructor(workerScript, poolSize = os.cpus().length) {
6 this.workerScript = workerScript;
7 this.poolSize = poolSize;
8 this.workers = [];
9 this.freeWorkers = [];
10 this.taskQueue = [];
11
12 this.init();
13 }
14
15 init() {
16 for (let i = 0; i < this.poolSize; i++) {
17 this.createWorker();
18 }
19 }
20
21 createWorker() {
22 const worker = fork(this.workerScript);
23
24 worker.on('message', (result) => {
25 worker.currentTask?.resolve(result);
26 worker.currentTask = null;
27 this.freeWorkers.push(worker);
28 this.processQueue();
29 });
30
31 worker.on('error', (error) => {
32 worker.currentTask?.reject(error);
33 this.workers = this.workers.filter(w => w !== worker);
34 this.createWorker();
35 });
36
37 this.workers.push(worker);
38 this.freeWorkers.push(worker);
39 }
40
41 processQueue() {
42 if (this.taskQueue.length === 0 || this.freeWorkers.length === 0) {
43 return;
44 }
45
46 const worker = this.freeWorkers.shift();
47 const task = this.taskQueue.shift();
48
49 worker.currentTask = task;
50 worker.send(task.data);
51 }
52
53 execute(data) {
54 return new Promise((resolve, reject) => {
55 this.taskQueue.push({ data, resolve, reject });
56 this.processQueue();
57 });
58 }
59
60 async shutdown() {
61 for (const worker of this.workers) {
62 worker.kill();
63 }
64 }
65}
66
67// Usage
68const pool = new WorkerPool('./compute-worker.js', 4);
69
70async function main() {
71 const results = await Promise.all([
72 pool.execute({ task: 'fibonacci', n: 40 }),
73 pool.execute({ task: 'factorial', n: 100 }),
74 pool.execute({ task: 'prime', n: 10000 }),
75 ]);
76
77 console.log('Results:', results);
78 await pool.shutdown();
79}Stdio Configuration#
1const { spawn } = require('child_process');
2
3// Inherit stdio (child uses parent's console)
4const interactive = spawn('vim', ['file.txt'], {
5 stdio: 'inherit',
6});
7
8// Pipe specific streams
9const mixed = spawn('command', [], {
10 stdio: ['pipe', 'pipe', 'inherit'], // stdin, stdout piped; stderr inherited
11});
12
13// Ignore streams
14const silent = spawn('command', [], {
15 stdio: 'ignore',
16});
17
18// Custom file descriptors
19const fs = require('fs');
20const out = fs.openSync('./out.log', 'w');
21const err = fs.openSync('./err.log', 'w');
22
23const logged = spawn('command', [], {
24 stdio: ['ignore', out, err],
25});
26
27// IPC channel
28const withIPC = spawn('node', ['child.js'], {
29 stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
30});
31
32withIPC.on('message', (msg) => {
33 console.log('Message via IPC:', msg);
34});
35
36withIPC.send({ hello: 'world' });Error Handling#
1const { spawn, exec } = require('child_process');
2
3// Handle spawn errors
4const child = spawn('nonexistent-command');
5
6child.on('error', (error) => {
7 console.error('Failed to start subprocess:', error.message);
8});
9
10// Handle exit codes
11const failing = spawn('node', ['-e', 'process.exit(1)']);
12
13failing.on('close', (code, signal) => {
14 if (code !== 0) {
15 console.error(`Process failed with code ${code}`);
16 }
17 if (signal) {
18 console.error(`Process killed with signal ${signal}`);
19 }
20});
21
22// Comprehensive error handling
23async function safeExec(command, options = {}) {
24 return new Promise((resolve, reject) => {
25 const child = exec(command, {
26 timeout: 30000,
27 maxBuffer: 1024 * 1024,
28 ...options,
29 }, (error, stdout, stderr) => {
30 if (error) {
31 error.stdout = stdout;
32 error.stderr = stderr;
33 reject(error);
34 } else {
35 resolve({ stdout, stderr });
36 }
37 });
38
39 child.on('error', reject);
40 });
41}
42
43// Usage
44try {
45 const result = await safeExec('risky-command');
46 console.log(result.stdout);
47} catch (error) {
48 console.error('Command failed:', error.message);
49 console.error('Stderr:', error.stderr);
50}Cross-Platform Commands#
1const { spawn } = require('child_process');
2const os = require('os');
3
4function crossSpawn(command, args = [], options = {}) {
5 const isWindows = os.platform() === 'win32';
6
7 if (isWindows) {
8 return spawn('cmd.exe', ['/c', command, ...args], options);
9 }
10
11 return spawn(command, args, options);
12}
13
14// Or use shell option
15const child = spawn('echo', ['hello'], {
16 shell: true, // Uses cmd.exe on Windows, /bin/sh on Unix
17});
18
19// Find executable
20function which(command) {
21 const isWindows = os.platform() === 'win32';
22 const cmd = isWindows ? 'where' : 'which';
23
24 return new Promise((resolve, reject) => {
25 exec(`${cmd} ${command}`, (error, stdout) => {
26 if (error) reject(error);
27 else resolve(stdout.trim().split('\n')[0]);
28 });
29 });
30}Best Practices#
Process Management:
✓ Always handle 'error' events
✓ Clean up child processes on exit
✓ Set appropriate timeouts
✓ Handle both stdout and stderr
Performance:
✓ Use spawn for streaming data
✓ Use fork for CPU-intensive tasks
✓ Implement worker pools for parallelism
✓ Reuse processes when possible
Security:
✓ Avoid shell: true with user input
✓ Sanitize command arguments
✓ Use execFile over exec when possible
✓ Limit resource usage
Error Handling:
✓ Check exit codes
✓ Handle signals properly
✓ Implement graceful shutdown
✓ Log stderr output
Conclusion#
Child processes enable parallel execution and system integration. Use spawn for streaming, exec for simple commands, fork for Node.js workers, and implement proper error handling. Worker pools maximize CPU utilization for heavy computations.