Command-line interface to the rescue with commander.js!
In a far, far away (and, to some, forgotten) time, machines didn’t have the fancy user interfaces we see today. It was a simpler time: the only form of input was the keyboard, the apps were called by writing their names, and the output was text-based as well.
But, despite this simplicity, people were forced to do everything on it. And, although less than today, there was a lot of demand to make use of it.
Since this interface was fairly restrictive, developers had to be very creative to make programs flexible enough to accept and do a lot for its users. Programs begun to accept parameters and options, allowing for a custom execution defined by its user.
Command-line interfaces or, in short, CLI (also called ‘command-line user interface’, ‘console user interface’ or ‘character user interface’) can be defined as parametric computer programs that receives its inputs (params and options) through a console interface or a script.
Interfaces nowadays have evolved all the way to the current graphical user interface (GUI), and the importance of CLI’s have diminished to the domains of system administration, developers and hardcore Unix fans (I’m guilty of being the last two things).
Although CLI’s are no longer close to their former glory, in the domain of developers it has been experiencing a resurgence. Many applications have been adding CLI’s to their GUI’s, (e.g. VS Code, Spotify, among others) and programming languages have been introducing CLI’s as mechanisms to simplify the development process with them (e.g. Node, Golang, etc.).
And you can develop one yourself, if you’d like. If you have scripts or some commands that are usually passed with the same parameters all the time or you have a sequence of actions that you would like to automate, then you would definitively benefit from a command-line interface. Here I’ll show you how you accomplish that using Node.js.
But first, let’s understand why CLI are still interesting.
Why CLI?
Automation has been on the spotlight for quite some time. But recently we have seen an increase in the need for automation as it brings a lot of benefits, not only to the individual, but to businesses as well.
Repetitive tasks leads to boredom, boredom leads to drop in motivation, decreasing productivity levels and increasing avoidable mistake occurrences.
Automation, not only let you focus in the real task at hand, it also diminishes the occurrences of mistakes (and in my humble opinion, correcting mistakes is less appealing than doing repetitive tasks).
Also, machines are known to be faster than humans (to certain types of problems). So, as long as a task can be automated, the machine can operate as fast as its hardware specs allows and it can jump from some task to another without any human intervention.
Command-line interface tools can be thought of as a cluster of automation scripts joined together by a human readable API. Therefore, all the advantages of script automation can be applied to CLI's as well, plus the bonus of flexibility and readability that comes with parameters and options.
But, then, why not GUI? While one could argue that a GUI possess the same benefits of a CLI and a more "friendly" approach, graphical user interfaces needs to be run on machines with GUI capabilities, consumes more machine resources, and they cannot be sewn together in a bigger and more customized tool. In addition to that, the development process of GUI is longer and more expensive than that of a CLI.
And, if you need to convince your boss that it is necessary, just show that it decreases time-to-market, lower costs and cheapens new-hires on-boarding. For more detailed arguments on these you can read this study of IBM and Forbes, and this other from McKinsey.
Making our own CLI
Although we could develop our own CLI framework (or just the parser part), we should avoid reinventing the wheel. There are lots of command-line interface frameworks out there. There are for python, javascript, golang and many others, just choose what fits you best in language and features.
For this tutorial, I’ll use commander.js (version 2.19.0), an open-source JS framework. Why? This was the one I’ve used at work to develop some CLI’s for my client and I’ve come to like it very much.
To be able to use the commander.js, and therefore the CLI developed in here, one would only need to have Node.JS v6 installed on his/her machine. Beware, this Node.JS version only refers to the minimum version commander.js supports. If you were to add other dependencies or Javascript features, you might need to bump the minimum Node.JS version.
Anatomy of commander.js
Before going into the code, it is wise to define some vocabulary we will be using from now on. This is commander.js specific, so your framework could have different names or features.
Commands are comparable to an executable. They are the programs that will receive the options and arguments and process them accordingly.
Arguments are the things you pass to a command for it to process.
Options are the ones that alter the command behavior. It might have a short form “-<some character>” or a long form “ — <some-string>”.
There is also the git-style mode. In a normal mode, it usually goes “program [options] [arguments]”. But sometimes, a program can separate its features into different sets, making entire new commands. Instead of creating various CLIs (one for each feature), we can create another command inside the actual command. It has received this name, because git works like that:
git [command] [options] [arguments]
Words are cheap, show me the code!
Lets start with the simplest example we can do in every project a developer make: hello-word! In this example, our goal is to simply print “Hello, World” to the console once we call our program. It is as easy as this:
This program can than be called like this:
$ node ./my.js
Hello, World!
Well, this is far too simple, it would be good if we could make the program receive some parameters (arguments). I have an idea, we could say hello to every guy we can remember the names of!
In the code above, we simply say that our program is prepared to receive 0+ names as arguments using the function .arguments(args). And in an .action(Function), we receive those names to be processed the way we want!
So:
$ node ./my.js Alpha Bravo Charlie
Hello, Alpha!
Hello, Bravo!
Hello, Charlie!
Things have begun to get interesting, but I have mentioned that one use case for a CLI is its capacity to hold many commands. Now, enters the git-style CLI. In the git-style, commander.js does not allow the use of .action(Function). We simply state the commands along with their descriptions.
In exchange, we now only need to create another file following the name rule: entry point file + ‘-’ + command name. For example, my entry point file was called my.js, so to be able call the hello command, I would need a my-hello.js file as stated below.
Everything is fine now, except one thing! I also stated that CLI programs can have their functionality altered by passing options. To do that, it is really easy, simply call .option(opts, description) like this:
Now, to use our new CLI, one can do:
$ node ./my.js --lower Alpha Bravo Charlie
hello, alpha!
hello, bravo!
hello, charlie!
Conclusion
Now, we have a (VERY) basic, but working, command-line interface tool to ourselves. To deploy it you could, for example, send it to npm, link to your npm packages locally, create an alias to the shell command, etc.
It doesn’t add much value to us, but using these techniques plus a lot more described in the commander.js documentation, you can create more complex constructs that bring real value to your use case.
There are other tools that can increase user experience in a CLI, such as inquirer and chalk, just from the top of my head. Such libraries can also be found in other language stacks, such as cobra and viper for Golang. But these are beyond the scope of this article and will be addressed later.
Good luck on your new journey into the CLI world! I expect great things from you!
Further reading
- https://en.wikipedia.org/wiki/Text-based_user_interface
- https://en.wikipedia.org/wiki/Command-line_interface
- https://slack.engineering/the-joy-of-internal-tools-4a1bb5fe905b
- https://timber.io/blog/creating-a-real-world-cli-app-with-node/
- https://nordicapis.com/the-return-of-the-cli-clis-being-used-by-api-related-companies/
- https://searchstorage.techtarget.com/magazineContent/GUIs-are-nice-but-dont-overlook-command-line-interfaces-BEST-PRACTICES