NodeJS CLI with Commander.js

nodejscommander.jsclijavascriptutility
NodeJS CLI with Commander.js code sample.

We use command line interfaces every day, such as git, ls, grep, and so on.
CLI or Command Line Interfaces are usually the fastest way to solve a problem, even today with all the Graphical Interfaces.

Command Line Interfaces were created in the 60s when computers (Mainframes) interacted with the user by text-based interfaces, and today they keep being very important besides the GUI(Graphical User Interfaces) rise, mainly because CLI is fast, reliable, and can be stacked together to create a script.

With the most rated and loved language, JavaScript, we will build a zero package CLI, and a CLI with the commander.js package, allowing us to create more complex, helpful, user-friendly Command Line Interfaces.

Bored stuff:

mkdir nodejs-cli ; cd nodejs-cli ; npm i -y ;

Part 1


In this first part, I will show you how to use only NodeJS to get the CLI arguments and build a straightforward calculator. Okay, I know another calculator, but hold on, let's focus on the CLI instead of the calculator for this one.

If you are only interested in Commander.js, please go to Part 2.

touch procargvs.js
# nvim
nvim procargvs.js
# or vscode
code procargvs.js

To access the command-line arguments, you can use the process.argv array provided by the Node.js runtime. This array contains the command-line arguments passed to the Node.js script, with the first argument being the path to the node executable and the second argument being the path to the JavaScript file being executed.

For us, the important arguments will be the third element forward.

How to get the arguments?

// procargvs.js
const args = process.argv
const myArgs = args.slice(2)
console.log(myArgs)

then,

node procargvs.js hi --hello -bye

Your output should be:

[ 'hi', '--hello', '-bye' ]

Now, the final CLI will work in this way, we are going to call node procargvs.js -sum <number> <number> <number...>.

To get to that point we need:

  • Get the arguments that start with -.
  • Verify the type: sum, sub, div, mul, and fac.
  • Return the correct value for the operation.

The first point, get the arguments that start with -.

// procargvs.js
const args = process.argv
const myArgs = args.slice(2)
// Gets the options eg. word that starts with '-'
const options = myArgs.filter((a) => a.startsWith(`-`));
const values = myArgs.filter((a) => !a.startsWith('-'));

Options are the ones like -help, and the values are the rest of the arguments, e.g., <number>.

The second point, verify the type: sum, sub, div, mul, and fac.

// procargvs.js
let result = ''
options.map((o)=> {
switch (o) {
case '-sum':
result += calculate('+', (p, v)=> Number(p)+Number(v))
break;
case '-mul':
result += calculate('*', (p, v)=> Number(p)*Number(v))
break;
case '-div':
result += calculate('/', (p, v)=> Number(p)/Number(v))
break;
case '-sub':
result += calculate('-', (p, v)=> Number(p)-Number(v))
break;
case '-fac':
result += calculateFactorial()
break;
default:
break;
}
})

The calculate() and calculateFactorial() are functions used to work out the result, they will be provided later. See going further.

Explanation: we are mapping over the options and verifying using a switch the type of operation, you can use an if statement too.

Result:

> node procargvs.js -sum 10 10
| 10 + 10 = 20

Part 2

In this final part, we will use the Commander.js package to handle all the arguments. And use the bin property on package.json to create a runnable CLI on your computer.
The CLI with the commander will be a little bit different, we are going to call like this node commander.js calculate -sum <number> <number> <number...>.

mkdir bin/ ; cd bin ; touch commander.js ; npm install commander ;
# then
nvim commander.js
# or
code commander.js

Let's import and initialize the commander:

// commander.js
import { Command } from 'commander'
const program = new Command()
program.name('node-js-cli-with-commander').description('CLI to run calculations.').version('0.0.1')

To specify the arguments we can use the program.command.option:

// commander.js
program
.command('calculate')
.description('Run a given calculation')
.argument('<number...>', 'numbers to calculate')
.option('--sum', 'sum', '')
.option('--mul', 'multiply', '')
.option('--div', 'divide', '')
.option('--sub', 'subtract', '')
.option('--fac', 'factorial', '')
.action((numbers, options) => {
if (options.sum) {
console.log(calculate('+', (p, v) => Number(p) + Number(v), numbers))
}
if (options.mul) {
console.log(calculate('*', (p, v) => Number(p) * Number(v), numbers))
}
if (options.div) {
console.log(calculate('/', (p, v) => Number(p) / Number(v), numbers))
}
if (options.sub) {
console.log(calculate('-', (p, v) => Number(p) - Number(v), numbers))
}
if (options.fac) {
console.log(calculateFactorial(numbers))
}
})
program.parse()

You can see that the commander package allows us easily to improve our CLI with a description of the program and for each command. It also pre includes a -h or --help command.

The calculate() and calculateFactorial() are functions used to work out the result, they will be provided later. See going further.

> node bin/commander.js --help
Usage: node-js-cli-with-commander [options] [command]
CLI to run calculations.
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
calculate [options] <number...> Run a given calculation
help [command] display help for command
> node bin/commander.js --version
0.0.1

Explanation:

  • .arguments will define the values after the option, in this case the numbers to calculate.
  • .options will defined each possible option of the command. option(flags: string, description?: string, defaultValue?: string | boolean | string[])

Let's try to use the program now to see how it works:

> node bin/commander.js -V
0.0.1
> node bin/commander.js calculate
error: missing required argument 'number'
> node bin/commander.js calculate -h
Usage: node-js-cli-with-commander calculate [options] <number...>
Run a given calculation
Arguments:
number numbers to calculate
Options:
--sum sum
--mul multiply
--div divide
--sub subtract
--fac factorial
-h, --help display help for command
> node bin/commander.js calculate -sum 10 10
10 + 10 = 20

Now that the CLI is working, let us wrap everything up and make it available to the PATH.

To accomplish this we are going to use the npm link command to link the local folder to the global package folder, with this we can test and run the cli without building and deploying.

We need to define the bin property on the package.json file. After that, we can start the linking process.

...
"bin": {
"mwc": "./bin/commander.js calculate",
"mwp": "./bin/procargvs.js calculate"
},
...

The first command to run is:

chmod u+x bin/*

This is presuming that all the executables are inside bin folder.

After that, you can run:

npm link

Test the mwc(Math With Commander) command:

> mwc
Usage: commander-js-calculator [options] [command]
CLI to run calculations.
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
calculate [options] <number...> Run a given calculation
help [command] display help for command

and then Test the mwp(Math With Procargvs) command:

> mwp -fac 5  ✔ 
------------------------------
!5 = 120
------------------------------

As you can see at this point you are able to build a simple and effective CLI with NodeJs.

I hope I have made you more productive and able to improve and advance in your career.

Thank you so much and I am looking forward to seeing you soon. 😉

And if you like content about Git, Linux, Productivity tips, Typescript, and Python please follow me Marco Antonio Bet, and pay me a coffee.

Going further

To learn more:

Full project on GitHub repository.

Procargvs

// procargvs.js
const args = process.argv
const myArgs = args.slice(2)
// Gets the options eg. word that starts with '-'
const options = myArgs.filter((a) => a.startsWith(`-`))
const values = myArgs.filter((a) => !a.startsWith('-'))
let result = ''
const renderValues = (operator, total) => {
return `\n| ${values.join(` ${operator} `)} = ${total}`
}
const calculate = (operator, func) => {
if (values && values.length > 0) {
const t = values.reduce(func)
return renderValues(operator, t)
}
return ''
}
const calculateFactorial = () => {
if (values && values.length > 0) {
let base = Number(values[0])
let t = base
for (let i = base - 1; i < base && i >= 1; i--) {
t *= i
}
return `\n!${base} = ${t}`
}
return ''
}
options.map((o) => {
switch (o) {
case '-sum':
result += calculate('+', (p, v) => Number(p) + Number(v))
break
case '-mul':
result += calculate('*', (p, v) => Number(p) * Number(v))
break
case '-div':
result += calculate('/', (p, v) => Number(p) / Number(v))
break
case '-sub':
result += calculate('-', (p, v) => Number(p) - Number(v))
break
case '-fac':
result += calculateFactorial()
break
default:
break
}
})
console.log('------------------------------', result, '\n------------------------------')

Commander

// commander.js
import { Command } from 'commander'
const program = new Command()
const renderValues = (operator, total, values) => {
return `\n${values.join(` ${operator} `)} = ${total}`
}
const calculate = (operator, func, values) => {
if (values && values.length > 0) {
const t = values.reduce(func)
return renderValues(operator, t, values)
}
return ''
}
const calculateFactorial = (values) => {
if (values && values.length > 0) {
let base = Number(values[0])
let t = base
for (let i = base - 1; i < base && i >= 1; i--) {
t *= i
}
return `\n!${base} = ${t}`
}
return ''
}
program.name('node-js-cli-with-commander').description('CLI to run calculations.').version('0.0.1')
program
.command('calculate')
.description('Run a given calculation')
.argument('<number...>', 'numbers to calculate')
.option('--sum', 'sum', '')
.option('--mul', 'multiply', '')
.option('--div', 'divide', '')
.option('--sub', 'subtract', '')
.option('--fac', 'factorial', '')
.action((numbers, options) => {
if (options.sum) {
console.log(calculate('+', (p, v) => Number(p) + Number(v), numbers))
}
if (options.mul) {
console.log(calculate('*', (p, v) => Number(p) * Number(v), numbers))
}
if (options.div) {
console.log(calculate('/', (p, v) => Number(p) / Number(v), numbers))
}
if (options.sub) {
console.log(calculate('-', (p, v) => Number(p) - Number(v), numbers))
}
if (options.fac) {
console.log(calculateFactorial(numbers))
}
})
program.parse()

Share?