Build Your Own CLI Application with Node.js

Today we are going to build a custom Command-line interface (CLI) application with Node.js. I’m planning to make a CLI to browse the topics of my blog. Let’s get started:

Table of Contents

  1. Initialize Project
  2. Create CLI Menus
  3. Create Index File
  4. Install Our App & Test

Initialize Project

Let’s initialize our CLI project:

# create project
mkdir mnp-cli && cd mnp-cli

# init package.json
npm init -y

Install a few libraries:

npm install --save commander inquirer colors pad open

The important packages are commander and inquirer. We’ve installed colors, pad to beautify our project. These are optional. Also we’ve installed open package to open URLs in the browser.

Now we need to edit package.json. We’re going to set mnp as our command name. To do this, we’ve to add a bin field like:

{
  "name": "mnp-cli",
  "version": "1.0.0",
  "description": "Mynotepaper CLI App",
  "bin": {
    "mnp": "./bin/index.js"
  },
  "author": "Md Obydullah",
  "license": "ISC",
  "dependencies": {
    "colors": "^1.4.0",
    "commander": "^5.1.0",
    "inquirer": "^7.1.0",
    "open": "^7.0.4",
    "pad": "^3.2.0"
  }
}

Create CLI Menus

Create a folder called lib and create a file under lib named data.js. We’ll store menu’s values in this file.

lib/data.js
// topics
exports.topics = [
  {name: 'Laravel', slug: 'laravel'},
  {name: 'Vue.js', slug: 'vue-js'},
  {name: 'Linux Server', slug: 'linux-server'},
];

 // convert topic names to one line
exports.topicsNamePlain = exports.topics.map(function(o) {
  return o.name;
});

// open in browser
exports.openInBrowser = [
  "Yes",
  "No"
];

Create another file named topics.js. This is our first menu. Here we’ll print menu names only.

lib/topics.js
const colors = require('colors');
const { topics } = require('./data');

// export function to list coffee
module.exports = function() {
    console.log('MNP TOPICS');
    console.log('------------------');

    // list on separate lines
    topics.forEach((topic) => {
        console.log('%s', colors.bold(topic.name));
    });
};

Let’s create another file called browse.js. This is our second menu. From here, we’ll show options to select a topic. Then we’ll ask user to open the topic URL in browser.

lib/browse.js
const inquirer = require('inquirer');
const colors = require('colors');
const pad = require('pad');
const data = require('../lib/data');
const open = require('open');

const questions = [
  { type: 'list', name: 'topic', message: 'Choose a topic', choices: data.topicsNamePlain },
  { type: 'confirm', name: 'openInBrowser', message: 'Open the topic in browser?', choices: data.openInBrowser },
];

module.exports = function () {
  inquirer
    .prompt(questions)
    .then(function (answers) {

      // get topic slug from topics array
      var getTopicArry = data.topics.filter(t => t.name == answers.topic);
      var getSlug = getTopicArry[0]['slug'];
      var topicUrl = 'https://shouts.dev/topics/' + getSlug;

      // print answers
      console.log('\n');
      console.log('YOUR ANSWERS');
      console.log('------------------');

      console.log(pad(colors.grey('Topic: '), 30), answers.topic);
      console.log(pad(colors.grey('Topic URL: '), 30), topicUrl);
      console.log(pad(colors.grey('Open in browser: '), 30), answers.openInBrowser);

      // open topic in browser
      if(answers.openInBrowser) {
        (async () => {
          // opens in the default browser
          await open(topicUrl);
        })();
      }

    });
};

Create Index File

Create a folder named bin and under bin folder create a file called index.js. We’ll add shebang line at the top of the file and will import commander and menus. We’ll also set command names here:

bin/index.js
#!/usr/bin/env node
const program = require('commander');

// import menus
const topics = require('../lib/topics');
const browse = require('../lib/browse');

// print topic menu
program
  .command('topics')
  .alias('t')
  .description('All topics')
  .action(function () {
    topics();
  });

// print browse menu
program
  .command('browse')
  .alias('b')
  .description('Browse a topic')
  .action(function () {
    browse();
  });

// allow commander to parse `process.argv`
program.parse(process.argv);

Install Our App & Test

It’s time to install our app globally from local source code. Run this command to install globally:

npm install -g ./

Now we’re able to use mnp command. Run this command to check if our command works or not:

# these commands will show same info:

# command 1
mnp
# command 2
mnp -h
# command 3
mnp help

The commands will show the info like:

Usage: index [options] [command]

Options:
  -h, --help      display help for command

Commands:
  topics|t        All topics
  browse|b        Browse a topic
  help [command]  display help for command

Yeah. Our CLI application is working. We’re seeing our all commands using the help command. Let’s test the topics and browse commands:

# command topics
mnp topics
# or
mnp t

# command browse
mnp browse
# or
mnp b

Have a look at the GIF file:

Note: Look at the Commander.js‘s GitHub repo to get more info.

We’ve created our own CLI app and tested. You can download this project from GitHub. Thank you.


Software Engineer | Ethical Hacker & Cybersecurity...

Md Obydullah is a software engineer and full stack developer specialist at Laravel, Django, Vue.js, Node.js, Android, Linux Server, and Ethichal Hacking.