Interactive Node JS CLI with Inquirer

PUBLISHED ON SEP 13, 2019 — JAVASCRIPT, NODEJS

This post builds on the code of my previous post. If you’re already familiar with Commander (or just don’t care about what I said earlier) feel free to read on and ignore that.
As always, the code is on GitHub which you can find here

Inquirer

Inquirer is a package that makes it easier to add a series of questions to your CLI program, accepting user input, and return you the answers.
This is especially useful for things like template generation (my use case) or just to simply also annoy your uses (possibly also my use case).

I’m not going to cover setting up the CLI app as my previous post addresses that, instead I’ll just note that for this example I’ve set up a single action through Commander called quiz. The aim of that action is to simply retrieve a set of questions, get input from the user, and print out some answers.
With Inquirer, you pass in an array of questions to be asked (in order) - the questions can be of multiple different types, I’ve tried to cover most of them in this post.
So let’s go through them tediously one by one.

Confirm

Confirm is pretty easy to cover - it’s a yes or no question.
Specify the type as confirm, and you can leave it at that if you choose. You can also specify a default value, which can be handy if you have an answer that’s correct most of the time to help speed up the process for your users:

{ type: 'confirm', name: 'confirmExample', message: 'A confirm example:', default: true}

List

List is, as may be slightly hinted at in the name, a way to have your users select an answer from a list of options.
The only difference here is that we provide an array of choices.
When the question is displayed to the user, they can use their arrow keys to navigate up and down the list and select an answer:

{ type: 'list', name: 'listExample', message: 'A list example:', choices: ['Item 1', 'Item 2', 'Item 3']}

You can still specify a default value, and that’s done here by using the index of the value you want as the default:

{ type: 'list', name: 'listExample', message: 'A list example:', choices: ['Item 1', 'Item 2', 'Item 3'], default: 2}

Checkbox

Checkbox is quite similar to list, in that we set up the object the same, providing an array of choices.
The difference is that when the type is set to checkbox the user will be presented the list with checkboxes next to them, and can use the space bar to select and de-select items on the list:

{ type: 'checkbox', name: 'checkboxExample', message: 'A checkbox example:', choices: ['Checky', 'Checky Check', 'Checky Check Check']}

Again here you can specify a set of default values, this time using an array.
What’s annoying though is that it’s not the indexes as before, but instead the actual values that need to be used:

{ type: 'checkbox', name: 'checkboxExample', message: 'A checkbox example:', choices: ['Checky', 'Checky Check', 'Checky Check Check’], default:[‘Checky’,’Checky Check Check’]}

Input

Input is used when you want to get input from the user, funnily enough.
Here the default if specified is the default string that will be used if the user just accepts it:

{ type: 'input', name: 'inputExample', message: 'This is tedious. Tell me about it:', default: 'This is a weird form of torture.'}

Number

Number is the same as input, expect this time you input only a number.
Mind-blowing, right?
If you don’t provide a number, you’ll be surprised to hear that the answer received will in fact be NaN:

{ type: 'number',   name: 'numberExample', message: 'Enter a number:'}

Expand

Expand is one that I find quite odd.
You provide an array of choices once again, however this time each ‘choice’ is a key-value pair. The key is what the user enters to expand and select an option, the value is the response you get.
I’m not sure on the use-case for this question type, but thought I’d include it anyway as it reminded me of Alan Partridge

{ type: 'expand',   name: 'expandExample', message: 'What is the point of this?', choices: [{key: 'x', name: 'Expand'}, { key: 'a', name: `Yes! It's an extender!`}]}

Password

Password is essentially the same as input, except it’s super secret and your input will be hidden (it’s not actually super secret, as you get the password entered back in plain text, but visually it counts)

{ type: 'password', name: 'passwordExample', message: `Password please. Shh I won't tell anyone:`}

Validation

While there are several other aspects of Inquirer you can use to customize and expand on things, the only one I’m going to cover right now will be one of the first to be needed - validating input.
You can add a validate field to the question object, defined as a function.
The function takes in the value input by the user as a parameter, and then in the function simply do whatever you’re wanting to do - return true if the validation passes, or a message to the user if it fails.
For example, I added validation in this example to the number input to check if the user actually input a number:

{ type: 'number',   name: 'numberExample', message: 'Enter a number',
    validate: function(value) {
        const valid = !isNaN(parseInt(value));
        return valid || "Please enter a number";
    }
}

Wrapping Up

To put this all together, I’ve added a quizzer.js action under lib/actions.
In this module I’ve added two functions - one to retrieve the questions, and one to process the answers.
I’ve also added an external dependency on colours colors - this is completely optional, I just wanted to make my output look a bit different.
The overall file looks like this:

'use strict';

require('colors');

const quizzer = {
    getQuestions: () => {
        return [
            { type: 'list',     name: 'listExample',        message: 'A list example to start with:', default: 2, choices: ['Item 1', 'Item 2', 'Item 3']},
            { type: 'checkbox', name: 'checkboxExample',    message: 'And now for a checkbox:', default: ['Checky', 'Checky Check Check'],      choices: ['Checky', 'Checky Check', 'Checky Check Check']},
            { type: 'confirm',  name: 'confirmExample',     message: 'Is this annoying you yet:',     default: true},
            { type: 'expand',   name: 'expandExample',      message: 'And now expand...',             choices: [{key: 'x', name: 'Expand'}, { key: 'a', name: `Yes! It's an extender!`}]},
            { type: 'input',    name: 'inputExample',       message: 'This is tedious. Tell me about it:', default: 'This is a weird form of torture.'},
            { type: 'number',   name: 'numberExample',      message: 'How high can you count:',
                validate: function(value) {
                    const valid = !isNaN(parseInt(value));
                    return valid || "Please enter a number";
                }
            },
            { type: 'password', name: 'passwordExample',    message: `Password please. Shh I won't tell anyone:`},
        ];
    },
    processAnswers: (answers) => {
        console.log('And now for the thrilling output:'.green);
        console.log(`You chose list item ${answers.listExample}`.blue);
        console.log(`And you checked items: ${answers.checkboxExample}`.blue);
        const bovvered = answers.confirmExample ? `yes, this is ridiculous` : `no, you're not annoyed`;
        console.log(`And you indicated that ${bovvered}`.blue);
        console.log(`You chose ${answers.expandExample} for the expandable question`.blue);
        console.log(`How tedious was all this? You said ${answers.inputExample}`.blue);
        console.log(`But at least you can count to ${answers.numberExample}`.blue);
        console.log(`Don't worry though. Your password is super secure. I won't tell anyone that it's '${answers.passwordExample}'`.blue);
    }
};

module.exports = quizzer;

As you can see it simply defines the questions as discussed above, and my ‘processing’ of answers is simply to print them out.
In there you’d do whatever it is you’re wanting to do - spin up some resources in Azure or AWS, place an order on your website, harvest their credit card information. You get the gist.

To call this code I’ve created a simple command in index.js using Commander.
In here I simply retrieve the questions, prompt the user through Inquirer, and call the function to process those answers:

'use strict';

const program = require('commander');
const inquirer = require('inquirer');

const quizzer = require('./lib/actions/quizzer.js');

program.name('node-cli').usage('command [options]');

program.command('quiz')
        .action(async () => {
            const questions = quizzer.getQuestions();
            const answers = await inquirer.prompt(questions);
            quizzer.processAnswers(answers);
        });

program.parse(process.argv);

And that’s all you need to get started building your own interactive CLI tool. How exciting.
Hopefully this post was of some use to someone other than myself, and if you just want to see the full code you can find it on my GitHub here.

comments powered by Disqus