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.