Node.js
Command Line Binary
JavaScript
Programming
Code Execution

Execute a command line binary with Node.js

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Node.js can run external command-line programs through the built-in child_process module. The real question is not whether Node can launch a binary, but which API you should use: exec, execFile, or spawn.

Choose the Right Child Process API

These three APIs solve slightly different problems:

  • 'exec runs a shell command string and buffers the output'
  • 'execFile runs a binary directly with argument arrays'
  • 'spawn starts a process and streams output incrementally'

If you are executing a real binary with known arguments, execFile is usually the safest default because it avoids shell parsing by default.

Use execFile for Normal Binary Execution

Here is a simple example that runs node --version:

javascript
1const { execFile } = require('node:child_process');
2
3execFile('node', ['--version'], (error, stdout, stderr) => {
4  if (error) {
5    console.error('failed to execute:', error);
6    return;
7  }
8
9  if (stderr) {
10    console.error('stderr:', stderr.trim());
11  }
12
13  console.log('stdout:', stdout.trim());
14});

This is a good fit when:

  • the process is short-lived
  • the total output is modest
  • you want argument handling without shell quoting problems

It is also much safer than concatenating user input into a shell command string.

Use spawn for Long-Running or Chatty Processes

Some binaries stream logs continuously or produce large output. In that case, spawn is a better choice because it gives you the process streams directly instead of buffering everything first.

javascript
1const { spawn } = require('node:child_process');
2
3const child = spawn('node', ['-e', "console.log('hello from child')"]);
4
5child.stdout.on('data', chunk => {
6  process.stdout.write(`stdout: ${chunk}`);
7});
8
9child.stderr.on('data', chunk => {
10  process.stderr.write(`stderr: ${chunk}`);
11});
12
13child.on('close', code => {
14  console.log(`child exited with code ${code}`);
15});

This is the right pattern for tools such as compilers, media encoders, package managers, or anything else that may emit a lot of output over time.

Use exec Only When You Need Shell Features

exec can be convenient because it accepts one command string. That also means a shell parses the string for you.

javascript
1const { exec } = require('node:child_process');
2
3exec('node --version', (error, stdout, stderr) => {
4  if (error) {
5    console.error(error);
6    return;
7  }
8
9  console.log(stdout.trim());
10});

Use exec when you actually need shell behavior such as pipes, redirection, or compound commands. Otherwise, prefer execFile or spawn.

Promise-Friendly Usage With async and await

Modern Node code often reads better with promises.

javascript
1const { execFile } = require('node:child_process');
2const { promisify } = require('node:util');
3
4const execFileAsync = promisify(execFile);
5
6async function runVersionCheck() {
7  const { stdout } = await execFileAsync('node', ['--version']);
8  console.log(stdout.trim());
9}
10
11runVersionCheck().catch(console.error);

That makes child-process code easier to integrate with the rest of an async application.

Working Directory and Environment Matter

External binaries often depend on the current directory, environment variables, or both. If the same command works in your terminal but fails in Node, check those assumptions first.

javascript
1const { execFile } = require('node:child_process');
2
3execFile('git', ['status'], { cwd: '/tmp' }, (error, stdout) => {
4  if (error) {
5    console.error(error);
6    return;
7  }
8
9  console.log(stdout);
10});

You can also pass an env object if the binary needs specific environment variables.

Common Pitfalls

One common mistake is using exec for large-output commands. Because it buffers output, it can hit memory or max-buffer limits more easily than spawn.

Another is constructing shell command strings from untrusted input. If a shell is involved, command injection becomes a real risk.

Developers also sometimes forget that localhost, relative paths, and PATH resolution may differ between their terminal and the Node process environment.

Finally, do not ignore exit codes and stderr. A process may produce partial output and still fail, and that failure is often the most important signal.

Summary

  • Use execFile for normal binary execution with explicit arguments.
  • Use spawn when output should be streamed or the process runs for a while.
  • Use exec only when shell features are actually needed.
  • Set cwd and env explicitly when the command depends on them.
  • Treat exit codes, stderr, and shell-injection risk as first-class concerns.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.