DEV Community

Cover image for Building your first interactive Node JS CLI
Hugo Dias
Hugo Dias

Posted on • Updated on

Building your first interactive Node JS CLI

Originally posted in my blog

NodeJS can be very useful when it comes to building Command-line Interfaces also known as CLI's.

In this post, I'll teach you how to build a CLI that asks some questions and creates a file, based on the answers.

Getting started

Let's start by creating a brand new npm package

mkdir my-script
cd my-script
npm init
Enter fullscreen mode Exit fullscreen mode

NPM will ask some questions. After that, we need to install some packages.

npm install --save chalk figlet inquirer shelljs
Enter fullscreen mode Exit fullscreen mode

What these packages do:

  • chalk - Terminal string styling done right
  • figlet - Figlet is a program for making large letters out of ordinary text
  • inquirer - A collection of common interactive command line user interfaces
  • shelljs - Portable Unix shell commands for Node.js

index.js file

Now create a index.js file with the following content:

#!/usr/bin/env node

const inquirer = require("inquirer");
const chalk = require("chalk");
const figlet = require("figlet");
const shell = require("shelljs");
Enter fullscreen mode Exit fullscreen mode

Planning the CLI

it's always good to plan what a CLI needs to do before writing any code.

This CLI will do just one thing: Create a file.

It should ask a couple of questions and after that, show a success message with the created file path.

The questions are: what is the filename and what is the extension.

// index.js

const run = async () => {
  // show script introduction
  // ask questions
  // create the file
  // show success message
};

run();
Enter fullscreen mode Exit fullscreen mode

The first function is the script introduction. Let's use chalk and figlet to get the job done.


const init = () => {
  console.log(
    chalk.green(
      figlet.textSync("Node f*cking JS", {
        font: "Ghost",
        horizontalLayout: "default",
        verticalLayout: "default"
      })
    )
  );
}

const run = async () => {
  // show script introduction
  init();

  // ask questions
  // create the file
  // show success message
};

run();
Enter fullscreen mode Exit fullscreen mode

Now it's time to write a function that asks questions.

const askQuestions = () => {
  const questions = [
    {
      name: "FILENAME",
      type: "input",
      message: "What is the name of the file without extension?"
    },
    {
      type: "list",
      name: "EXTENSION",
      message: "What is the file extension?",
      choices: [".rb", ".js", ".php", ".css"],
      filter: function(val) {
        return val.split(".")[1];
      }
    }
  ];
  return inquirer.prompt(questions);
};

// ...

const run = async () => {
  // show script introduction
  init();

  // ask questions
  const answers = await askQuestions();
  const { FILENAME, EXTENSION } = answers;

  // create the file
  // show success message
};
Enter fullscreen mode Exit fullscreen mode

Notice the constants FILENAME and EXTENSIONS that came from inquirer.

The next step is to create the file.

const createFile = (filename, extension) => {
  const filePath = `${process.cwd()}/${filename}.${extension}`
  shell.touch(filePath);
  return filePath;
};

// ...

const run = async () => {
  // show script introduction
  init();

  // ask questions
  const answers = await askQuestions();
  const { FILENAME, EXTENSION } = answers;

  // create the file
  const filePath = createFile(FILENAME, EXTENSION);

  // show success message
};

Enter fullscreen mode Exit fullscreen mode

And last but not least, show the success message along with the file path.

const success = (filepath) => {
  console.log(
    chalk.white.bgGreen.bold(`Done! File created at ${filepath}`)
  );
};

// ...

const run = async () => {
  // show script introduction
  init();

  // ask questions
  const answers = await askQuestions();
  const { FILENAME, EXTENSION } = answers;

  // create the file
  const filePath = createFile(FILENAME, EXTENSION);

  // show success message
  success(filePath);
};
Enter fullscreen mode Exit fullscreen mode

Let's test the script by running node index.js.

Screenshot

Yay! And here is the final code:

Final code

#!/usr/bin/env node

const inquirer = require("inquirer");
const chalk = require("chalk");
const figlet = require("figlet");
const shell = require("shelljs");

const init = () => {
  console.log(
    chalk.green(
      figlet.textSync("Node f*cking JS", {
        font: "Ghost",
        horizontalLayout: "default",
        verticalLayout: "default"
      })
    )
  );
};

const askQuestions = () => {
  const questions = [
    {
      name: "FILENAME",
      type: "input",
      message: "What is the name of the file without extension?"
    },
    {
      type: "list",
      name: "EXTENSION",
      message: "What is the file extension?",
      choices: [".rb", ".js", ".php", ".css"],
      filter: function(val) {
        return val.split(".")[1];
      }
    }
  ];
  return inquirer.prompt(questions);
};

const createFile = (filename, extension) => {
  const filePath = `${process.cwd()}/${filename}.${extension}`
  shell.touch(filePath);
  return filePath;
};

const success = filepath => {
  console.log(
    chalk.white.bgGreen.bold(`Done! File created at ${filepath}`)
  );
};

const run = async () => {
  // show script introduction
  init();

  // ask questions
  const answers = await askQuestions();
  const { FILENAME, EXTENSION } = answers;

  // create the file
  const filePath = createFile(FILENAME, EXTENSION);

  // show success message
  success(filePath);
};

run();
Enter fullscreen mode Exit fullscreen mode

To execute this script anywhere add a bin section in your package.json file and run npm link

{
  "name": "creator",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "chalk": "^2.4.1",
    "figlet": "^1.2.0",
    "inquirer": "^6.0.0",
    "shelljs": "^0.8.2"
  },
  "bin": {
    "creator": "./index.js"
  }
}

Enter fullscreen mode Exit fullscreen mode
$ npm link
$ creator
Enter fullscreen mode Exit fullscreen mode

Hope it helps :)


Photo by Alex Knight on Unsplash

Top comments (4)

Collapse
 
andreybleme profile image
Lucas Bleme

Great tutorial Hugo. There are also many people writing CLI tools using Go, have you ever built any CLI tool using Go? Any thoughts?

Collapse
 
hugodias profile image
Hugo Dias

Thanks Lucas.

Yes, Go is also amazing on building CLIs! Never tried before but it should be really fast and easy to work with files.

Maybe in a near future!

Any tips?

Collapse
 
rjmunhoz profile image
Rogério Munhoz (Roz)

Great tutorial!

Another nice thing to do is using argv (npmjs.org/package/argv) in order to allow non-interactive or silence execution by providing the answers to the questions through command line arguments

Collapse
 
nelsonomuto profile image
Nelson Omuto • Edited

This is super awesome dude!

I wanted it to stay alive and all I had to do was make the run function recurse.

This is great work, keep it up