How to create a Node.js interactive Command Line Interface (CLI)

January 31, 2021

Tested with Ubuntu Desktop 18.04 LTS and Node.js v14.15.4 LTS

Usually I write shell scripts and CLI using Bash and / or Python. But well, I mainly spend my days writing JavaScript to create Web applications and on my workstation Node.js is the king (alongside with Python). So, why do not use JavaScript even for shell?

In this post, we are going to familiarize with the basic concepts, specially how to read the user input, without using third-party libraries. Just Node.js!

At the end we will have a script that allows the user to display the environment variables, the CLI help and to quit the CLI.

Let is begin:

  1. create a folder, e.g. node-cli-example

    $ mkdir node-cli-example
  2. create a file called node-cli-example.run

    $ touch node-cli-example.run
  3. set executable permissions

    $ chmod u+x node-cli-example.run
  4. open the file with our favorite text editor
  5. the first line of the file must be

    #!/usr/bin/env node

    this set the interpreter to be used. In this case node

  6. we are going to create an interactive CLI, so we need something that prints a prompt and waits for user input. Node.js provides a module called readline and it is perfect for our purposes.

      const readline = require("readline");
     
      // instantiate a readline
      const cli = readline.createInterface({
          input: process.stdin,
          output: process.stdout,
          prompt: "\n> ",
      });
     
      // prompts the user and waits for input
      cli.prompt();
     
      // listen to user input
      cli.on("line", (line) => {
          console.log(line);
     
          cli.prompt();
      });
      
  7. from the console we can run the script typing

    $ ./node-cli-example.run

    At the prompt, type Hello, World! and press Enter. The script should print our input and should show a new prompt ready for the next input. To exit press Ctrl + C.

  8. Nice, now we have the scafolding. Let add the possibility to quit without the need to press Ctrl + C. Let assume that if the user type quit, the CLI must quit. The code within the listener becomes:

      cli.on("line", (line) => {
          const input = line.trim();
     
          switch (input) {
              case "quit": {
                  console.log("Bye!");
     
                  process.exit(0);
              }
              default: {
                  console.log(line);
              }
          }
     
          cli.prompt();
      });
      
  9. Well, the CLI is going to grow with the env command, so we need an help to remeber all the available commands.
    Let add to the switch the env case.
    Let add the help case that is even the default. This means that if we type help or just press Enter the CLI will display the help.
    The code within the listener becomes:

      cli.on("line", (line) => {
          const input = line.trim();
     
          switch (input) {
              case "env": {
                  console.log(line);
     
                  break;
              }
              case "quit": {
                  console.log("Bye!");
     
                  process.exit(0);
              }
              case "help":
              default: {
                  console.log(
      "\nCommands available\n\
      --------------------------------------\n\n\
      env      display environment variables\n\n\
      quit     quit the CLI\
      "
      );
              }
          }
     
          cli.prompt();
      });
      
  10. And now let implement the env command. The case statement for env becomes:

    case "env": {
        for (const variable in process.env) {
            console.log(`${variable}: ${process.env[variable]}`);
        }
    
        break;
    }
    

That is it! We created our interactive CLI! Of course, it is possible extends this small example to manage more complex scenarios, like commands with arguments.

References

  1. Readline module documentation


A photo of Elia Contini
Written by
Elia Contini
Sardinian UX engineer and a Front-end web architect based in Switzerland. Marathoner, traveller, wannabe nature photographer.