Creating HelloWorld CLI Command with NodeJS
Tags: javascript, node, beginners, cli
Published: 2023-08-23 19:14:02

---
title: Creating HelloWorld CLI Command with NodeJS
published: true
description: A tutorial showing how to use NodeJS to create a CLI command that processes optional parameters.
tags: javascript, nodejs, beginners, cli
canonical_url: https://crs1138.me/creating-helloworld-cli-command-with-nodejs
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6nu8gbv6g54arkf90yr3.jpg
# Use a ratio of 100:42 for best results.
# published_at: 2023-08-23 19:06 +0000
---

I have recently returned from the World Congress conference organised by WeAreDevelopers in Berlin. It's been the second time I attended and I saw some very inspiring talks. One of them caught my attention in particular. It was Phil Nash's (Sonar) talk titled *4 Steps from Javascript to Typescript*. Apart from the lesson on Typescript, he was using NodeJS to create a CLI command.

Now, that's nothing unseen before, we all use various CLI commands written in JS (think npm, trash-cli, etc.), but it has never occurred to write my own. In the past, when I needed a CLI script, I wrote it in ZSH. However, one of them was getting a bit too complex that I caught myself thinking: "Next time I'll write this in another language." At Phil's talk, I had that a-ha moment. Of course, I can use JS!

## The Brief

This is my first little exercise to learn the ins and outs of creating a CLI command in NodeJS. This is the brief I gave myself.

> Create a CLI command that accepts zero to many arguments and displays:
> 
> 1. "Hello world!" for no arguments.
>     
> 2. "Hello \[name\]!" if one argument is provided.
>     
> 3. "Hello \[name1\] and \[name2\]!" for two arguments.
>     
> 4. "Hello \[name1\], \[name2\], … \[nameX\]!" for three and more arguments.
>     

## Pre-requisites

* [Node](https://nodejs.org/) – get some recent version, 18+, it will likely work on ancient versions too, but if you have to use an old version of Node, you probably know what you're doing anyway.
    
* [PNPM](https://pnpm.io/installation) – Node package manager
    

## The Solution

### TLDR;

For those of you who are results-oriented, you can [find the repo on my Github account](https://github.com/crs1138/hello-world-cli).

### Create a Node package

First I've created a new Node package. Nowadays, my package manager of choice is [`pnpm`](https://pnpm.io/) – it's fast, more performant and takes up less space on the disk thanks to the way it deals with Node modules.

```bash
# Create and enter a new folder
take hello-world-cli
# Initialise a new Node package
pnpm init
```

This will create a *package.json* file with the basic default metadata. We will need that file for installing our dependencies.

### Basic file structure

```bash
./
├── LICENSE
├── README.md
├── bin
│   └── hello
├── node_modules
├── package.json
├── pnpm-lock.yaml
└── src
    ├── cli.js
    └── greetings.js
```

### The CLI command `hello`

First, we create the file *hello*: `touch /bin/hello`. This is gonna be the actual command that the user will launch. Make sure to make it executable `chmod a+x ./bin/hello`.

```javascript
#!/usr/bin/env node

const { run } = require('../src/cli');
run(process.argv).catch((err) => console.error(err));
```

As you can see I specify the [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) to execute this file using node as an interpreter. This enables us to use JS to import the `run()` function from the `/src/cli.js` and call it asynchronously passing the array of arguments ([`process.argv`](https://nodejs.org/docs/latest/api/process.html#processargv)) from the command line as its parameter. The first element `process.argv[0]` is read-only and is usually reserved for the absolute path of the processor (in my case: `/Users/myuser/.nvm/versions/node/v20.5.1/bin/node`. The second array item `process.argv[1]` is the path to the command we're running: `/Users/myuser/Sites/_tuts/hello-word-cli/hello`.

In case there's an error, I catch it and display it in the console.

### `run()` – process the command

First of all, let's install [yargs](https://www.npmjs.com/package/yargs) package to make our life easier processing all the possible parameters: `pnpm install yargs`. I will touch just the surface of what this package can do for you but, this is a good template even for more complex CLI commands.

This is the code in `/scr/cli.js`.

```javascript
const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');
const { showGreeting } = require('./greetings');
async function run(args) {
    const { _: names } = yargs(hideBin(args)).argv;
    showGreeting(names);
}
module.exports = { run };
```

I import the `yargs()` function and its helper function `hideBin()`. The `hideBin(args)` is a shorthand for `args.slice(2)`. This way, you can make your command work even in not-so-standard environments like [Electron](https://github.com/electron/electron/issues/4690).

I define the asynchronous function `run(args)` that takes an array as its argument. Process this argument using the `yargs()` and `hideBin()` functions.

I desctructure the `_` property from the `argv` returned by `yargs` and rename this variable as `names` for the convenience of my code and pass the `names` to my `showGreeting(names)` function.

That's it! As far as the processing of a CLI command is concerned, we're done. The rest of it is just plain JS as usual.

### `showGreeting(names)` - display the right greeting

The file `/src/greetings.js` serves as a library of functions related to displaying greetings.

```javascript
const joinTwoNames = (twoNames) => {
    return `${twoNames.join(' and ')}`;
};

const getLongGreetings = (longListOfNames) => {
    return longListOfNames.reduce(
        (acc, name, currentIndex) =>
            currentIndex < longListOfNames.length - 1
                ? `${acc}${name}, `
                : `${acc.slice(0, acc.length - 2)} and ${name}`,
        ''
    );
};

const getGreetings = (names) => {
    const greetingsList = [
        'Hello world!',
        `Hi there ${names[0}!`,
        `Hi there ${joinTwoNames(names)}!`,
        `Hi there ${getLongGreetings(names)}!`,
    ];
    const greetingsIndex = names.length >= 3 ? 3 : names.length;
    return `${greetingsList[greetingsIndex]}`;
};

const showGreeting = (names) => {
    const greeting = getGreetings(names);
    console.log(greeting);
};

module.exports = {
    getGreetings,
    showGreeting,
};
```

I export the `showGreeting()` function as well as `getGreetings()` for convenience even though I don't use `getGreetings()` anywhere else for now.

There is a list of string templates saved in the `getGreetings(names)` function that is used to decide which greeting to use depending on how many names are passed as its argument.

1. No names? Fine, it's just `Hello world!`.
    
2. One name? Easy, `Hi there, ${names[0]}` will print the first (and only) item of the `names` array.
    
3. For two names, I have a helper function `joinTwoNames(twoNames)` that concatenates the two names in the array adding the `and` in between them and this is then used in the `greetingList[2]` string template.
    
4. Any more than three names will end up using the `getLongGreetings(longListOfNames)` function that will rely on the high-order method `array.prototype.reduce()` to concatenate all individual names with a comma as its separator apart from the last two that will be linked by the conjunction `and`.
    

## That's it, folks!

I hope you found this useful, please let me know if you have any questions or comments. I intended to demonstrate in a simple and easy-to-understand way the basics of creating a simple CLI command in Node.

---
_Cover image by Emma Plunkett © 2016_