DEV Community

Lokesh_Choudhary
Lokesh_Choudhary

Posted on • Updated on

How to Build a Command Line Interface (CLI) Application with Node.JS👨‍🎓🤓.

What is a Command Line Interface (CLI) Application?

CLI tools allow you to run certain tasks or operations right from your terminal or command line prompt. As a developer , chances are you spend most of your time in your terminal, typing in commands to help you get around some tasks. A good example of commonly used applications include npm , Create React App , Vue CLI etc.
In this tutorial you will create a CLI todo list application by using Node JS.

Note

This is the GitHub Repo Link Go Here for the finished project. The CLI App work fine , for any specific reason it it not working just use the clone the repo and try it once and the tutorial will give you full understanding of all the concepts.

Setting up the project

First , create a directory that will hold the CLI application:

    mkdir todo-list-cli
    cd todo-list-cli
Enter fullscreen mode Exit fullscreen mode

Next, we will initialize our Node.js project

    npm init -y
Enter fullscreen mode Exit fullscreen mode

This will create a package.json file with defaults. Once that is done you need to install some packages that will help create our CLI application. These are:

  1. commander:This package makes creating the CLI application easier. It provides functions that will allow you to set the commands, options and more

  2. chalk:This package lets us print colored messages to the console. It will help us make our CLI application look nice and pretty

  3. conf:This package allows us to save persistent information on the user’s machine. We will be using it to save the user’s todo list

To install these packages, run:

    npm i commander chalk conf
Enter fullscreen mode Exit fullscreen mode

Once installation is done, you are ready to start the development of the CLI tool.

Creating a CLI Application

Firstly start by creating index.js in the root of the project. This will be the main entry of the application.

Next, to create the basic configurations, and functionalities, we can use commander. First, let's require program from commander.

const {program} = require('commander')
Enter fullscreen mode Exit fullscreen mode

To declare a command, commander provides the following functions:

  1. command: takes a string that defines the command.
  2. description: describes the command for the user. This is helpful when the user uses the --help option.
  3. option: the options that this command can take, if any.
  4. action: particular action to execute, when command is executed.

If you are wondering what about the commands for the CLI?

You need the following commands:

  1. todo list: this will list the tasks in the user’s to-do list
  2. todo add: this will add a new task to the user’s to-do list
  3. todo mark-done: this will mark specific tasks or all tasks as done in the list

Creating the List Command

The list command will just show the list of tasks that the user has added before. It will not take any options. You should be able to run it by running the following command in your terminal:

    todo list
Enter fullscreen mode Exit fullscreen mode

Now in index.js, add the following below the code we added earlier:

    program
          .command('list')
          .description('List all the TODO tasks')
          .action(list)
Enter fullscreen mode Exit fullscreen mode

In this the command function is used to declare the command in CLI application. The argument you pass is a string.The description function is used to describe this command to the user when they run the application with the --help option. Finally, the action is assigned to a function called list, which you will create shortly.

Now, create a new directory called commands. And a file list.js in it, which will hold the function that will run when the user runs todo list in the terminal.

For storing and reading the tasks, you will use the conf package. It has the following functions:

  1. set: this sets the information under a specific key
  2. get: this gets the information that is set under a specific key

Lets start by requiring conf in commands/list.js:

    const conf = new (require('conf'))()
Enter fullscreen mode Exit fullscreen mode

Next, you need to implement and export the function for use in index.js:

    function list(){

    }
    module.exports = list
Enter fullscreen mode Exit fullscreen mode

Now, specify the key todo-list inside the list function under which the data will be set and get using the conf package. todo-list will be an array of objects.

    const todoList = conf.get('todo-list')
Enter fullscreen mode Exit fullscreen mode

Next, if todo data is already in the todo-list, you need to loop over them and display them in the terminal, done tasks will have green color and yellow for the tasks that are not done.

If todo-list is empty, which means user does not have any tasks, you need to show a message in red indicating that they don't have any tasks.

    const todoList = conf.get('todo-list')

    if (todoList && todoList.length) {
      //user has tasks in todoList
    } else {
      //user does not have tasks in todoList
    }
Enter fullscreen mode Exit fullscreen mode

To show color in our terminal, as mentioned before you will use chalk. Let's require it in the commands/list.js.

    const conf = new (require('conf'))()
    const chalk = require('chalk')

    //rest of our code
Enter fullscreen mode Exit fullscreen mode

Next, you will use chalk in the else part first. You need to show the user that they don't have any tasks in the todo list. To display message in red using chalk :

    else {
      //user does not have tasks in todoList
      console.log(
      chalk.red.bold('You don\'t have any tasks yet.')
     )
    }
Enter fullscreen mode Exit fullscreen mode

Now, you need to display a message in green if the user does have tasks. First, you need to show a message that details the color meaning of the tasks.

    if (todoList && todoList.length) {
     console.log(
        chalk.blue.bold('Tasks in green are done. Tasks in yellow are still not done.')
     )
    }
Enter fullscreen mode Exit fullscreen mode

Now, the second step is to loop over the tasks in the todoList and for each task, check if it's done then display green, if not then yellow.

    todoList.forEach((task, index) => {
         if (task.done) {
             console.log(
                 chalk.greenBright(`${index}. ${task.text}`)
             )
         } else {
             console.log(
                 chalk.yellowBright(`${index}. ${task.text}`)
            )
          }
     })
Enter fullscreen mode Exit fullscreen mode

Now the list function is done and you can use it in index.js.

The full code till now is:

    const conf = new (require('conf'))()
    const chalk = require('chalk')
    function list () {
        const todoList = conf.get('todo-list')
        if (todoList && todoList.length) {
            console.log(
                chalk.blue.bold('Tasks in green are done. Tasks in yellow are still not done.')
            )
            todoList.forEach((task, index) => {
                if (task.done) {
                    console.log(
                        chalk.greenBright(`${index}. ${task.text}`)
                    )
                } else {
                    console.log(
                        chalk.yellowBright(`${index}. ${task.text}`)
                    )
                }
            })
        } else {
            console.log(
                chalk.red.bold('You don\'t have any tasks yet.')
            )
        }
    }
    module.exports = list
Enter fullscreen mode Exit fullscreen mode

Now, in the index.js require the list function.

    const list = require('./commands/list')
Enter fullscreen mode Exit fullscreen mode

Then, at the end of file add the following:

    program.parse()
Enter fullscreen mode Exit fullscreen mode

This is important for commander as the input of the user need to be parsed to figure out which command the user is running and execute it.

Testing the Application

The first step is to add the following in the package.json file.

    "bin":{
        "todo":"index.js"
    }
Enter fullscreen mode Exit fullscreen mode

todo will be used in the terminal when running commands from our todo CLI. You can change it to whatever you want. It is pointing at index.js, as this is your main point of entry.

Now, you have to globally install the package on your machine.

    npm i -g
Enter fullscreen mode Exit fullscreen mode

Once it is done you can run the application right from your terminal.

    todo --help
Enter fullscreen mode Exit fullscreen mode

You will see the following in your terminal:

    Usage: todo [options] [command]

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

    Commands:
      list                 List all the TODO tasks
      help [command]       display help for command
Enter fullscreen mode Exit fullscreen mode

If you run the list command:

    todo list
Enter fullscreen mode Exit fullscreen mode

It will just show the message that you don't have any task yet.
Let's implement a new command that is add command.

Add Command

The add command will take one argument, which will be the text/title of the task.The command will look.

    todo add "Make CLI App"
Enter fullscreen mode Exit fullscreen mode

Now, you need to declare add command in index.js under the list command, not after the program.parse(), add the the following:

    program
        .command('add <task>')
        .description('Add a new TODO task')
        .action(add)
Enter fullscreen mode Exit fullscreen mode

The command function has add where is the argument the user need to pass. In commander a required argument is , whereas if it’s optional, you use [ARG_NAME]. Also, the name given to the argument is same as name of the parameter passed to the function in action.

Now, just like the list command you need to implement the add function. Let’s create the file commands/add.js with the following :

    const conf = new (require('conf'))()
    const chalk = require('chalk')

    function add (task) {

    }

    module.exports = add
Enter fullscreen mode Exit fullscreen mode

In the add function, a task parameter is passed, which will be passed by the user.

The add function will store the task in the todo-list array using conf. And a success message in green will be displayed using chalk.

First, you will use the get function to get the todo-list and then push a new task to the todo-list array.

The entire code for the add function is:

    function add (task) {
        //get the current todo-list
        let todosList = conf.get('todo-list')

        if (!todosList) {
            //default value for todos-list
            todosList = []
        }

        //push the new task to the todos-list
        todosList.push({
            text: task,
            done: false
        })

        //set todos-list in conf
        conf.set('todo-list', todosList)

        //display message to user
        console.log(
            chalk.green.bold('Task has been added successfully!')
        )
    }
Enter fullscreen mode Exit fullscreen mode

Now, go back to index.js and require the add function:

    const add = require('./commands/add')
Enter fullscreen mode Exit fullscreen mode

Let's test this command in the terminal:

    todo add "Make CLI App"
Enter fullscreen mode Exit fullscreen mode

You will get a message "Task has been added successfully!" in green. To check that added task, run in your terminal:

    todo list
Enter fullscreen mode Exit fullscreen mode

Try adding some more tasks to see the list grow.

Now will add the mark-done command which will mark a task as done.

mark-done command

The mark-done command, takes a --tasks option followed by at least one index of the tasks user want to mark as done it mark those as done, but by default will mark all tasks as done if not index is specified.

Example command:

    todo mark-done --tasks 1 2
Enter fullscreen mode Exit fullscreen mode

For the simplicity of the tutorial, the indices of tasks to mark them done are used.But in a real-life use case application, you would probably use unique IDs for the tasks.

Now, declare mark-done command under the add command:

    program
        .command('mark-done')
        .description('Mark commands done')
        .option('-t, --tasks <tasks...>', 'The tasks to mark done. If not specified, all tasks will be marked done.')
        .action(markDone)
Enter fullscreen mode Exit fullscreen mode

In this command the option function. The first parameter is the format of the option -t, --tasks means that the user can use either -t or --tasks to pass this option. means that more than one task can be provided, but as the <> is used which means it is required. The second parameter is the description of the option. This is useful when the user types todo mark-done --help command.

Now, just like previous commands you need to implement the markDone function. Let’s create the file commands/markDone.js with the following :

    const conf = new (require('conf'))()
    const chalk = require('chalk')

    function markDone({tasks}) {

    }
    module.exports = markDone
Enter fullscreen mode Exit fullscreen mode

You can see that the markDone function takes an object that includes a tasks property. If the -t or --tasks option is passed to the command, tasks will be an array of the values passed by the user. If not, it will be undefined.

What we need to do inside the markDone function is to get the todo-list array from conf. If todo-list is not empty, you need to loop over it. And mark only the tasks of the indices the user enters as done. If tasks indices is undefined, then mark all tasks as done.

The complete code for markDone is :

    function markDone({tasks}) {
        let todosList = conf.get('todo-list')

        if (todosList) {
            //loop over the todo list tasks
            todosList = todosList.map((task, index) => {
                //check if the user specified the tasks to mark done
                if (tasks) {
                    //check if this task is one of the tasks the user specified
                    if (tasks.indexOf(index.toString()) !== -1) {
                        //mark only specified tasks by user as done
                        task.done = true
                    }
                } else {
                    //if the user didn't specify tasks, mark all as done
                    task.done = true
                }
                return task
            });

            //set the new todo-list
            conf.set('todo-list', todosList)
        }

        //show the user a message
        console.log(
            chalk.green.bold('Tasks have been marked as done successfully')
        )
    }
Enter fullscreen mode Exit fullscreen mode

First check if the todoList is empty or not, if not loop over todosList inside map function. And then check if tasks is defined, which means if the user has passed any specific tasks to mark as done.

If tasks is defined, then you need to check if the current task item is in the tasks by checking if the index is in the tasks array. Note that index.toString() is used because the tasks array will hold the indices as strings.

If tasks is not defined, then, as mentioned before, mark all items as done. After loop completion you get the the updated list, you need to set todo-list using conf.set to the new array. In the end, show the user a success message.

Finally, go back to index.js and require markDone function:

    const markDone = require('./commands/markDone')
Enter fullscreen mode Exit fullscreen mode

Now, test it out. By running:

    todo mark-done
Enter fullscreen mode Exit fullscreen mode

If everything is correct, you can run todo list and see that all items are in green now.

Next, try to add a few more tasks then mark those done using their indices, an example command to mark a single task as done:

    todo mark-done -t 1
Enter fullscreen mode Exit fullscreen mode

To mark multiple tasks:

    todo mark-done -t 1 3 5
Enter fullscreen mode Exit fullscreen mode

You can now play with the commands then check which are marked done and which aren’t using the todos list command.

Our CLI Application is done! todo-list-cli now allows the user to add tasks, view them, and mark them done.

Conclusion

Congratulations, you learned how to create a CLI Application using Node.JS. The number of possible projects are are endless, so go create something more awesome!

Discussion (8)

Collapse
shivamsoni1737 profile image
Shivam Soni • Edited on

Well, I'm doing it in VScode and after running installing [npm i -g], when I use [todo --help] , it leads me to VScode again when I'm adding [todo add "Hello World"], it creates empty add file and hello world file. Not working for me, any suggestions?

Collapse
lokeshchoudharylc profile image
Lokesh_Choudhary Author

No problem , I will just have a look again and let you know , and will just also provide a github repo link also .

Tell me one thing , you used the program on mac , linux or windows ?

Thanks for reaching out for the problem it will also help others.

Collapse
shivamsoni1737 profile image
Shivam Soni

On windows, using command prompt

Thread Thread
lokeshchoudharylc profile image
Lokesh_Choudhary Author

Github link this is the github link and let me know again after trying it ...
Code works fine on my system(Windows, but also works on linux and mac too) , didn't found any problem not that I know of ...

Collapse
utsavladani profile image
Utsav Ladani

Also add inquirer.js

You can take input like true/false, check box, text, password using inquirer.js very easily.

Collapse
lokeshchoudharylc profile image
Lokesh_Choudhary Author

Yes inquirer.js is also a awesome library and I will also do a tutorial of it.

Thanks for the suggestion.

Collapse
3ddpaz profile image
Ed

nice work.

Collapse
lokeshchoudharylc profile image
Lokesh_Choudhary Author

Hope you like the project , any other ideas about tutorials/projects will be much appreciated.