This tutorial is out of date and no longer maintained.
As a developer, chances are you spend most of your time in your terminal, typing in commands to help you get around some tasks.
Some of these commands come built into your Operating System, while some of them you install through some third-party helper such as npm
, or brew
, or even downloading a binary and adding it to your $PATH
.
A good example of commonly used applications includes npm
, eslint
, typescript
, and project generators, such as Angular CLI, Vue CLI or Create React App.
This is the wikipedia definition:
In computing, a shebang is the character sequence consisting of the characters number sign and exclamation mark (
#!
) at the beginning of a script.
Whenever you look at any scripting file, you’ll see characters such as the ones below at the beginning of the file.
They serve as a way for your operating system program loader will help parse the correct interpreter for your executable file. This only works in Unix Systems though.
Node.js too has its own supported shebang characters. Make sure you have Node.js installed, and create a file called logger
with the following content.
The first line tells the program loader to parse this file with NodeJS. The rest of the file is just normal JavaScript.
You can try and run the file by typing this in your terminal. You’ll get a permission denied for execution.
Outputzsh: permission denied: ./logger
You need to give the file execution permissions. You can do that with.
You’ll see the file log correctly.
OutputI am a logger
This could also easily be achieved if we did node logger
. But we want to have our own command, and not need to use node ...
to run it.
Now that you have a teaser, we’re going to look at two example applications that we can build to learn more about CLI apps.
qod
) cli tool, that retrieves quotes of the day from https://quotes.rest/qod.Let’s create a directory and call it qod
:
And inside, instantiate a Node.js app:
Next, we know we need to make requests to the quotes server, so we could use existing libraries to do just this. We’ll use axios
We’ll also add a chalk
, a library to help us print color in the terminal.
We then write the logic needed to retrieve these quotes. Edit the qod
file.
The above is normal Node.js code and all we are doing is calling an API endpoint. Change the file permissions with:
Then run the application:
OutputThe best way to not feel hopeless is to get up and do something. Don’t wait for good things to happen to you. If you go out and make some good things happen, you will fill the world with hope, you will fill yourself with hope. - Barack Obama
It should appear green, as we stated with the color.
This is roughly the same as our first logger, but the point was to show that we can use existing libraries and still run our apps the same way.
This will be a bit more complex, as it will involve data storage and retrieval. Here’s what we’re trying to achieve.
todo
new
, get
, complete
, and help
. So the available commands will be:Create a new todo:
Get a list of all your todos:
Complete a todo item:
Print the help text
Seems straightforward.
Let’s create a directory called todo
:
And instantiate a Node.js app:
npm install -y
Note: If you do a lot of creating directories and getting into the directories, you can save yourself some time by adding a function like this to your bash or zsh file.
Then, all you need to do is type in mkd directory_name
, and you’ll be in there.
We’ll also install chalk
again so that we can log with colors.
The first thing we’re going to do is make sure we have these commands available. To get the commands working, we’ll use Node.js process/argv
which returns a string array of command-line arguments
The process.argv property returns an array containing the command line arguments passed when the Node.js process was launched
Add this to the todo
file.
Give the file executable permissions, and then run it with a new command.
You’re going to get this output.
Output[ '/Users/ganga/.nvm/versions/node/v8.11.2/bin/node',
'/Users/ganga/Dev/scotch/todo/todo',
'new' ]
Notice that the first two strings in the array are the interpreter and the file full path. In this case, these are my absolute pathnames. Then, the rest of the array contains whatever we passed, in this case, it’s new.
To be safe, let’s restrict these so that we can only accept the correct number of arguments, which is one, and they can only be new
, get
, and complete
.
Modify the todo
file as shown below.
We’ve first assigned the command line arguments to a variable, and then we check at the bottom that the length is not greater than three.
We’ve also added a usage
string, that will print what the command line app expects. Run the app with wrong parameters like below:
Outputonly one argument can be accepted
todo helps you manage your todo tasks.
usage:
todo <command>
commands can be:
new: used to create a new todo
get: used to retrieve your todos
complete: used to mark a todo as complete
help: used to print the usage guide
If you run it with one parameter, it will not print anything, which means the code passes.
Next, we need to make sure only the four commands are expected, and everything else will be printed as invalid.
Add a list of the commands at the top of the code.
And then check with the passed in command after we’ve checked the length.
Now, if we run the app with an invalid command:
We get this:
Outputinvalid command passed
todo helps you manage you todo tasks.
usage:
todo <command>
commands can be:
new: used to create a new todo
get: used to retrieve your todos
complete: used to mark a todo as complete
help: used to print the usage guide
We’ll cover the rest of the logic in separate topics. For now, you can combine the argument checks into a single function as an exercise.
This is the most straightforward part of the app. All we need to do is call the usage
function. Let’s add this to the todo
file.
We have a switch statement that will call functions based on what command has been called. If you look closely, you’ll notice the help
case just calls the usage function.
While writing this I also noticed that the default
case can serve as an error handler for invalid commands, so I deleted the check we did earlier and the commands array. Thought I’d leave it there because refactoring is always fun.
The new command will create a new todo item, and save it in a JSON file. We will use a file db
, to help us with the storage files.
The library we will use is lowdb
. We could easily write functions to read and write to a JSON file if we wanted to.
Install lowdb:
npm install --save lowdb
Let’s add readline
and lowdb
dependencies, to help us with storing data. The lowdb
code is standard from their GitHub page.
Next, we’ll add a function to prompt the user to input data.
Here we are using the readline
library to create an interface that will help us prompt a user to and then read the output.
Next, we need to add a function that will be called when a user types in the new
command
We’re using chalk
to get the blue color for the prompt. And then we will log the result.
Lastly, call the function in the new
case.
When you run the app now with the new command, you will be prompted to add in a todo. Type and press enter.
OutputThis my todo aaaaaaw yeah
You should see something similar to this.
Notice also, that a db.json
file has been created in your file system, and it has a todos
property.
Next, let’s add in the logic for adding a todo. Modify the newTodo
function.
Run the code again.
If you look at your db.json
, you’ll see the todo added. Add two more, so that we can retrieve them in the next get
command. Here’s mine:
With knowledge from the new command, the get
command should be simple to write.
Create a function that will retrieve the todos.
Running the app now should give us this:
Output1. Take a Scotch course
2. Travel the world
3. Rewatch Avengers
You can make the color fancy and green by using chalk.green
.
The complete command is a little bit complicated.
We can do it in two ways.
./todo complete
, we could list all the todos, and then ask them to type in the number/key for the todo to mark as complete../todo get
, and then choose the task to mark as complete with a parameter, such as ./todo complete 1
.Since we covered how option 1 is done in the new
command, we’ll look at option 2.
A good problem introduced here is the ./todo complete 1
, which will surely surpass our validity check for the number of commands given. We therefore first need to handle this. Change the function that checks the length of the arguments with to this.
The above approach is using the truth tables, where TRUE && FALSE
will equal FALSE
, and the code will be skipped when complete
is passed.
We’ll then grab the value of the new argument and make the value of todo as completed like so
I’ve made comments on each part of the code above. Please read through.
Make sure to update the switch statements.
When you run this with ./todo complete 2
, you’ll notice your db.json
has changed to this, marking the second task as complete.
The last thing we need to do is change ./todo get
to only show tasks that are done. We’ll use emojis for this.
When you now type in the ./todo get
you’ll see this.
Output1. Take a Scotch course
2. Travel the world ✔ ️
3. Rewatch Avengers
I know, it could be improved, but you get the idea.
That’s it for now.
This is JavaScript, and of course, some brilliant person out there has thought of all these and written helper libraries. The focus of this article was to look at how CLI applications are built with vanilla Node.js, but when working in the real world, it would be more productive to use libraries.
Here’s a list of helpful libraries to help you write awesome CLI applications, which you can publish to npm.
vopral
- fully featured interactive CLI frameworkmeow
- CLI helper librarycommanderjs
- CLI libraryminimist
- for arguments parsingyargs
- arguments parsingAnd not to mention libraries like chalk
that helped us with colors.
I hope you got a thing or two about building CLI applications with Node.js.
A common practice I did not mention is that, once your app is working fine, most libraries put the file into a bin
folder, and this way, npm knows how to work with executables. This is not a requirement, but regardless of where you place the executable, you should update package.json
bin property.
As an exercise, try to add a Delete
command to the CLI.
Happy Coding.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!