DEV Community

Cover image for Get Started Building CLI Tools with Node.js
José Miguel Álvarez Vañó
José Miguel Álvarez Vañó

Posted on • Originally published at jmalvarez.dev

Get Started Building CLI Tools with Node.js

In this post we will see how to build a CLI tool with Node.js. The only requirement is to use Node v18+. You can use a lower version of Node, but you will need to adapt some of the code to make it work.

Initialize project

Create a folder for your project and run npm init in this folder. This will create a package.json file in the root of your project. You can pass the -y flag to accept the default values.

npm init -y
Enter fullscreen mode Exit fullscreen mode
{
  "name": "cli-course",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
Enter fullscreen mode Exit fullscreen mode

By default, the module system in the project is going to be CommonJS or CJS. Imports and exports in CJS work like this:

var myModule = require("my-module");

var result = myModule.doSomething();

module.exports = {
  result,
};
Enter fullscreen mode Exit fullscreen mode

In ECMAScript modules or ESM it works like this:

import myModule from "my-module";

export const result = myModule.doSomething();
Enter fullscreen mode Exit fullscreen mode

If we want to use ESM we have to enable it in package.json by setting the type property to module.

{
  "name": "cli-course",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "type": "module"
}
Enter fullscreen mode Exit fullscreen mode

Parse arguments

In Node we can access the process variable globally. If you print to the console the content of process you will see a lot of information. In the argv property you can access the arguments passed to the script. For example, running node index.js will print something similar to:

console.log(process.argv);

/**
 * Output:
 * ["/usr/local/bin/node", "/projects/node-cli-course/index.js"]
 */
Enter fullscreen mode Exit fullscreen mode

As you can see, the first two elements of the array are the path to the node executable and the path to the script. If we pass more arguments to the command, they will be added to the argv array. For example, running node index.js --name=Jose will print this:

["/usr/local/bin/node", "/projects/node-cli-course/index.js", "--name=jose"]
Enter fullscreen mode Exit fullscreen mode

We can easily parse the flags passed to the script checking which arguments start with a - character.

const flags = [];

process.argv.forEach((arg) => {
  if (arg.startsWith("-")) {
    flags.push(arg.replaceAll("-", ""));
  }
});
Enter fullscreen mode Exit fullscreen mode

Using the command (node index.js --hi) the flags array would equal ['hi'].
And then we can do something with the flag.

if (flags.includes("hi")) {
  console.log("Hello World!");
}
Enter fullscreen mode Exit fullscreen mode

Read user input

Node has a built-in module to read user input called Readline.

To read user input we have to create a new interface and associate it with an input and output. In this case we are going to use the standard input and output which is directly available in the process variable. Using the interface object we can very easily ask questions to the user.

Once we finish, we have to close the interface to stop the process.

import * as readline from "node:readline/promises";

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

const answer = await rl.question("What's your name? ");

console.log(`Your name is ${answer}`);

rl.close();

/**
 * Output:
 * What's your name? Jose
 * Your name is Jose
 */
Enter fullscreen mode Exit fullscreen mode

Make script globally executable

An easy way of making the script globally executable is to create a new file which will execute the script.
The name of this new file has to be the same as the command name. For example, in our case I want to use the command name cli-course. So I created a file called cli-course that executes my script file.

node index.js
Enter fullscreen mode Exit fullscreen mode

And then run chmod +x cli-course to make the new file executable.

Finally, add the path of the project to the PATH environment variable.

export PATH=$PATH:/path/to/project/
Enter fullscreen mode Exit fullscreen mode

Now you can run globally the script by typing cli-course in the terminal.

$ cli-course
What's your name? Jose
Your name is Jose
Enter fullscreen mode Exit fullscreen mode

Conclusion

Following these steps you can create a basic CLI tool with Node.js. We've learned how to print text to the console, how to parse arguments, how to read user input and how to make the script globally executable.

If this is not enough you can use libraries like Inquirer.js. With this library you can:

  • provide error feedback
  • ask questions
  • parse input
  • validate answers
  • manage hierarchical prompts

It is up to you to decide if you need an external library or not for your tool.

Resources

Top comments (0)