Node.js CLI Input

In this tutorial, we will learn how to get user input in a Node.js CLI application. To do this, you will need to listen to STDIN (Standard Input), which Node.js exposes as process.stdin, a readable stream.

Streams are used to deal with I/O. You can learn more about it in this documentation.

1. Prerequisites

  • You should have an understanding of the JavaScript language.
  • You should have Node.js installed on your computer.

2. Project setup

To get started, let’s set up our project.

Create a new directory called node-cli-input.

Inside the directory, run:

npm init -y

This will generate a package.json file.

Once that’s done, create a new file called index.js to write our code.

3. Readline package

The readline package is a built-in package in Node.js. readline is a wrapper around the standard I/O.

Let’s import the readline package into our index.js file.

const readline = require('readline');

We should create a new readline interface object using the readline.createInterface() method and configure the readable and writable streams. Let’s set the input and output streams to process.stdin and process.stdout respectively.

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});

We can ask a question to the user using the rl.question() method.

The rl.question() method takes 2 arguments:

  • String: This string will be displayed as the question to the user.
  • Callback function: The rl.question() method will wait until the user provides input. Once the user provides input, this callback function will be executed. The callback function will get the user’s input as an argument.

NOTE: We should close the streams using the rl.close() method inside the callback function. If not closed, the process will remain in an idle state.

For example:

rl.question("Your favorite color? ", (input) => {
  console.log(input);
  rl.close();
});

Output:

Your favorite color? Black
Black

You can add an event listener for the close streams event using the rl.on() method.

rl.on('close', () => {
  console.log('Streams Closed')
})

Output:

Your favorite color? Black
Black
Streams Closed

You can learn more about the readline package from it’s documentation.

4. Callback hell

The problem with the rl.question() method is, it doesn’t return a Promise. Thus, we can’t use async/await to pause the flow of the program until the user provides the input.

If you want to get multiple user inputs in sequence, you have to do it within a callback function, like this:

rl.question("Question 1? ", (answer1) => {
  // do stuff

  rl.question("Question 2? ", (answer2) => {
    // do stuff

    rl.question("Question 3? ", (answer3) => {
      // do stuff

      rl.question("Question 4? ", (answer4) => {
        console.log(answer1, answer2, answer3, answer4);
        rl.close();
      });
    });
  });
});

As you can see, this can quickly get out of control and the code will be hard to manage.

5. Async iterator

Asynchronous iteration allows us to iterate over data that comes asynchronously, on-demand. You can create an async iterator that iterates through each line in the input stream.

If you’d like to learn more about Async iterators, refer to this article.

for await (const line of rl) {
  // Each line in from the input stream will be available successively here as `line`.
}

We can’t use the await keyword outside of an async function. So, we need to wrap all of our code inside an async function.

async function main() {
  // do your stuff here

  for await (const line of rl) {
    // Each line in from the input stream will be available here as `line`.
  }
}

main();

Let’s create a new function called input() to prompt the user and get input.

We can use the Symbol.asyncIterator of the readline object to get the next value in the input stream.

async function input(prompt) {
  console.log(prompt);
  return (await rl[Symbol.asyncIterator]().next()).value;
}

Now, we can use this function to get the value from the input stream and use the await keyword to pause the execution until we get the input from the user.

async function main() {
  const name = await input("May I have your name? ");
  const color = await input("Your favorite color? ");

  console.log(name, color);
  rl.close();
}

main();

6. Readline sync package

If you don’t mind installing an external package, that will increase the bundle size of the CLI application you are building, you can use the readline-sync package to get the input from the user in a synchronous manner.

Let’s install the readline-sync by running:

npm install readline-sync

Now, let’s import the package.

const readlineSync = require("readline-sync");

Similar to the readline package, you can use the readlineSync.question() method to prompt the user for input.

Unlike the readline package, you don’t have to pass a callback function to this function. The readlineSync.question() method will return the user’s input.

let input = readlineSync.question('May I have your name? ');
console.log(`Hi ${input}!`);

The readlineSync package also has other functions like readlineSync.keyInYN()readlineSync.keyInSelect(), etc.

The readlineSync.keyInYN() is used to get the user’s response from a single key without pressing the Enter key. The function will return true if “Y” was pressed or false if something else was pressed.

if (readlineSync.keyInYN('Yes?')) {
  // 'Y' key was pressed.
} else {
  // Another key was pressed.
}

The readlineSync.keyInSelect() is used to prompt a user to choose an item from a list. The function will return the number the user selected. The user doesn’t have to press the Enter button when we use this function.

let colors = ['Black', 'White', 'Gray', 'Yellow', 'Blue'];

let index = readlineSync.keyInSelect(colors, 'Favorite Color?');
console.log(colors[index]);

Output:

[1] Black
[2] White
[3] Gray
[4] Yellow
[5] Blue
[0] CANCEL
 
Favorite Color? [1...5 / 0]: 2
White

You can learn more about the readline-sync package and the other methods that are available from it’s npm page.

7. Let’s recap

  1. We used the readline package to prompt input from the user.
  2. We added an event listener for the readline stream’s close event.
  3. We used the async iterator to write an async function to get the input from the user to prevent callback hell.
  4. We used the readline-sync package to prompt input from the user synchronously.

Congratulations, 🥳 You did it.

Thanks for reading!

Related posts:

Web Scraping With Node.js
A Deep Dive Into Eleventy Static Site Generator
How to Build a Static site with Gatsby.js
React To The Future With Isomorphic Apps
Keeping Node.js Fast: Tools, Techniques, And Tips For Making High-Performance Node.js Servers
Building A Real-Time Retrospective Board With Video Chat
Node.js Rxjs
Better Error Handling In NodeJS With Error Classes
How to use TypeScript with Node.js
How to Integrate B2C M-Pesa API using Node.js
The Guide To Ethical Scraping Of Dynamic Websites With Node.js And Puppeteer
Building a RESTful API with Adonis.js
Getting Started with Node.js Rate Limiting
Writing A Multiplayer Text Adventure Engine In Node.js (Part 1)
Choosing Between NPM and Yarn
Introduction to Sequelize ORM for Node.js
Generating Authentication Token for Agora Applications
Debugging a Node.js app running in Docker using Nodemon and the Docker extension
Consuming the TinEye Reverse Image Search API in Node.js
Build and Dockerize a Full-stack React app with Node.js, MySQL and Nginx
Build a Twitch Chatbot in Node.js
Developing A Chatbot Using Microsoft’s Bot Framework, LUIS And Node.js
Getting Started with Node.js Child Processes
How to build a real time chat application in Node.js
How to Generate Fake Data in Node.js Using Faker.js
The History of Node.js
Using Slack To Monitor Your App
Writing A Multiplayer Text Adventure Engine In Node.js: Game Engine Server Design (Part 2)
Data Encryption and Decryption in Node.js using Crypto
A Vanilla Node.js REST API without Frameworks such us Express
Open-sourced node.js modules at Browserling
Getting Started With Axios In Nuxt