HVRDHVRD
CommanderJS

Building a Simple CLI

Step-by-step guide to create a CLI tool using commander.js and plain Node.js to count words or lines in a file, plus comparison.

What is Commander.js?

Commander.js is a powerful Node.js library for building CLI applications.

It provides:

  • Command parsing
  • Option handling
  • Help generation

All with a clean and declarative API.

Using commander.js makes building CLIs in Node.js easy and clean.

  • Handles argument parsing.
  • Automatically generates help.
  • Supports options and arguments out of the box.

Goal

We will build a CLI tool that:

  • Accepts a file path as an argument.
  • Has options to count:
    • Number of words (--words)
    • Number of lines (--lines)

Approach 1: Using Commander.js

1. Initialize Project

mkdir file-counter-cli
cd file-counter-cli
npm init -y
npm install commander

2. Create CLI with Commander.js

#!/usr/bin/env node

const fs = require('fs');
const { Command } = require('commander');
const program = new Command();

program
  .name('file-counter')
  .description('CLI to count words or lines in a file')
  .version('1.0.0')
  .argument('<file>', 'Path to the file')
  .option('-w, --words', 'Count words')
  .option('-l, --lines', 'Count lines')
  .action((file, options) => {
    fs.readFile(file, 'utf8', (err, data) => {
      if (err) {
        console.error('Error reading file:', err.message);
        process.exit(1);
      }

      if (options.words) {
        const wordCount = data.split(/\s+/).filter(Boolean).length;
        console.log(`Word count: ${wordCount}`);
      }

      if (options.lines) {
        const lineCount = data.split(/\r?\n/).length;
        console.log(`Line count: ${lineCount}`);
      }

      if (!options.words && !options.lines) {
        console.log('Please provide --words and/or --lines');
      }
    });
  });

program.parse();

Approach 2: Plain Node.js without Commander.js

Create Simple CLI

#!/usr/bin/env node

const fs = require('fs');

// Get arguments manually
const args = process.argv.slice(2);
const file = args.find(arg => !arg.startsWith('--'));
const options = args.filter(arg => arg.startsWith('--'));

if (!file) {
  console.error('Error: No file specified.');
  process.exit(1);
}

fs.readFile(file, 'utf8', (err, data) => {
  if (err) {
    console.error('Error reading file:', err.message);
    process.exit(1);
  }

  if (options.includes('--words')) {
    const wordCount = data.split(/\s+/).filter(Boolean).length;
    console.log(`Word count: ${wordCount}`);
  }

  if (options.includes('--lines')) {
    const lineCount = data.split(/\r?\n/).length;
    console.log(`Line count: ${lineCount}`);
  }

  if (!options.includes('--words') && !options.includes('--lines')) {
    console.log('Please provide --words and/or --lines');
  }
});

Comparison

FeatureCommander.jsPlain Node.js
Argument ParsingBuilt-in, declarativeManual, error-prone
Help GenerationAuto-generatedNot available by default
Option HandlingSupports aliases, defaultsManual string checks
Code CleanlinessClear, readable APIMessy with manual logic
Recommended Use CaseComplex CLI toolsSimple or one-off scripts

Example Usage

With Commander.js

file-counter sample.txt --words --lines

Without Commander.js

node index.js sample.txt --words --lines