Simple interactive CLI tool in Node/Javascript
Tags: cli, node, javascript, learning
Published: 2024-03-25 15:06:19

I was always fascinated with CLI tools provided by different applications, thinking that how simple and easy commands on CLI can result into such a big actions which can take quite a time to navigate through the UI.

At my workplace I had one task that has to be done manually every time there's a need of doing it, which is a step-by-step process so I was thinking of automating it my level. I had two options, one is creating a small UI and other one is creating CLI tool. Me already wanting to create a CLI tool obviously chose a CLI tool 🤩.

---
Here I will give you a small example about how can you create simple but very effective CLI tool in `nodeJS` Using [Commander](https://www.npmjs.com/package/commander) and [inquirer](https://www.npmjs.com/package/inquirer) packages.

## Requirement
If you're going to use `inquirer`

> NodeJS version >= `v18.12.0`


We're going to create a small Burger maker tool, which will accept inputs about bread, patty, toppings etc
- `Commander` is used to create CLI tool with commands and flags.
- `inquirer` provides ability to ask questions, interactivity and actions on the basis of answers.

## Implementation

Start a new javascript project with `npm init -y`
`inquirer v9` uses **esm modules** hence you cannot use `commonjs` syntax, to change your project to use module import/export change `type` property in your `package.json` file to `module`. You can read more about esm modules [here](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).

Import `commander` and `inquirer`
```js
import { Command } from 'commander';
import inquirer from 'inquirer';
```

create a new Program using `Command` class and add name along with some description.
```js
const program = new Command();
program
    .name('burger-maker')
    .description('Order a burger from CLI')
    .version('1.0.0');

program.parse();
```
Now, try running your basic command on terminal.
It should display command description with available options.


![burger-maker help](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2te0e69tq2f894gb59ng.png)

Now, add `order` command to the program and create basic questions using inquirer `prompts`
```js
const prompts = [
    {
        name: 'name',
        message: 'Your Name?',
        validate: function(name) {
            if (!name) {
                return "Name is required"
            }
            return true
        }
    },
    {
        name: 'bun',
        message: 'Choose a bun?',
        type: 'rawlist',
        choices: ['Classic', 'Whole Wheat', 'Gluten Free'],
        default: 'Classic'
    },
    {
        name: 'patty',
        message: 'Choose a patty?',
        type: 'rawlist',
        choices: ['Veg', 'Chicken', 'Today\'s Special'],
        default: 'Veg'
    },
    {
        name: 'toppings',
        message: 'Choose your favourite toppings',
        type: 'checkbox',
        choices: ['Tomato', 'Lettuce', 'Cheese', 'Onion']
    },
    {
        name: 'sauces',
        message: 'Choose your favourite sauces',
        type: 'checkbox',
        choices: ['Mayonnaise', 'Ketchup', 'Mustard']
    },
]
```
These prompts are self explanatory, there are different types of prompts offered by **inquirer** like `input, list, rawlist, checkbox, password` etc. Prompts with documentations can be found here [github](https://github.com/SBoudrias/Inquirer.js?tab=readme-ov-file#prompts)

Add `action` listener on created `order` command like below
```js
.command('order')
.description('Order a burger')
.action(async function(){
    const answers = await inquirer.prompt(prompts)
    console.log(answers) 
})
```
Now, when you run your `order` command you'll get prompts to input the order details.

![order command output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1xh52xgx479rw2vqldzn.png)

At this point you can do whatever with the response, you can make `http` api call and save in database.
You can also make nested prompts to gather more information about particular item like below
```js
const answers = await inquirer.prompt(prompts)
console.log(answers)
if (answers.patty === 'Chicken') {
     const pattyAnswers = await inquirer.prompt([]) // new prompts here
}
```
I also added small spinner by [nanospinner](https://www.npmjs.com/package/nanospinner) and printed placed order details after a small delay.

---

## Make it Globally available

- Update `main` property in package.json to `index.js` 
- Add `#! /usr/bin/env node` at the top of your file (this lines tells your OS to run this file with node interpreter)
- Add `bin` property to package.json as follows.
```json
"bin": {
    "burger-maker": "./index.js"
}
```
- Run `npm install -g .` to install this app 
- Now, open a new terminal and run `burger-maker -V`

![Final command output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9iq8gtx3ush09jemxach.png)

NOTE: 

- if you're using Typescript give a path to a `dist/index.js` file
- If you're using `nvm` make sure correct node version to run the command

### ✅ There you go, You have your first CLI tool.
---
My example code is available on [Github Repo](https://github.com/rohit-ambre/burger-maker-cli/).