Let's build a simple Node.js CLI application
Tags: node, tutorial, beginners, webdev
Published: 2021-08-22 19:28:08

Hello everyone! Hope you all are staying safe. 

Today, we are going to see how to quickly create a Node.js CLI application. This is a true beginners post. If you have experience creating Node.js CLI applications, then I don't think you will learn more from here. You could always read on and see. You may learn something new. 

Now that's out of the way. Let's begin.

## What are we building? 

**CLI** is the **Command Line Interface**. A better understanding would be obtained by calling it terminal on your computer. CLI apps are apps that run on the terminal. Minimal visual effect, maximum efficency and productivity is the tagline of CLI apps. 

Hmm, I thought about what would be a good introduction to Node.js CLI Application.  One of the most used CLI commands would be the `ls` command. Let's reproduce that using Node.js. We will create a `nls`.

## Tools of the trade

### Node.js
Node.js is a JavaScript runtime in the terminal (or outside of the browser). It's a wonderful piece of technology that allows JavaScript developers to create fully featured backend systems with their exisiting JavaScript knowledge.  Read more [here](https://nodejs.org/en/).

### npm - Package Manager
A package manager. 1000's of open source packages that you can try and use to build great things. Easy to install and get started, a great tool in the toolbox of any JS developer.
**FUN FACT** You would think npm stands for `Node Package Manager`, that's a misunderstanding most people make. It is actually a recursive bacronymic abbreviation for "npm is not an acronym". https://github.com/npm/cli

I think that's it for now. Let's build something. 

First create a folder called nls. `cd` into nls. Make sure you have node and npm setup. 

Run `npm init -y`. This will create a package.json. This is a config file for your application. It will list the dependencies you have used, names, description and much more. 

Exercise for you: Why did we use `-y` ? what happens if we don't. Figure it out.

The first thing we need to do is to create an `index.js` in the nls directory. Cool. 

We can now go into the API Docs of the Node.JS to see what we can use. Visit [Here](https://nodejs.org/dist/latest-v14.x/docs/api/). Make sure you are following for the version you have. I am running the 14.x LTS version. So I will use that. The sidebar on the left lists the different standard library and API's available for your node projects. Look through it. You will see something called File System. Load it up. It's a vast vast document. Don't feel overwhelmed. You can go ahead and search for readdir. There are three versions of the `readdir` function available for our use. 
1) call back based one.[Doc](https://nodejs.org/dist/latest-v14.x/docs/api/fs.html#fs_fs_readdir_path_options_callback)
2) Synchronous readdir. [Doc](https://nodejs.org/dist/latest-v14.x/docs/api/fs.html#fs_fs_readdirsync_path_options)
3) promises based one. [Doc] (https://nodejs.org/dist/latest-v14.x/docs/api/fs.html#fs_fspromises_readdir_path_options)

Please read through them. You don't need to understand everything. It would be really good for you to read through it and get an idea of the difference. We will use the callback based one to start with. Although I would probably prefer a promise based approach in a large project. Let's write some code. 

Start by requiring the fs module. ( we could use the ECMAScript module system to have taste of it. It's widely available now and I expect node packages to move to import/export rather quickly.Infact I will write another post on using the new import/export node API soon. See [more](https://nodejs.org/dist/latest-v14.x/docs/api/esm.html) if you are interested. )

```
const fs = require('fs')
```
next we use the readdir function.readdir accepts three arguments. the first one is a path. This is the path of the directory from which you want to read the contents. The second is options objects. It has options like `encoding` and `withFileType`. Note that. We will use that one. The last is a callback function that will allows us to execute the code we want after readdir runs. The callback accepts two arguments. `err` and `files`. Okay. 

```
// process.cwd() is the way by which node understands the 
// current working directory. We will change it soon. 
// Give me 2 minutes :)
fs.readdir(process.cwd(), (err, files) => {
 if(err) {
  console.error('something went wrong!');
  return;
 }
 console.log(files)
})
```
How do we test it out? Well node makes it easy. Go to your package.json. somewhere in that, without breaking the JSON structure add 
```
// package.json
// You can replace nls with whatever you want. This is what 
// your ls command is going to be. Get creative. 
"bin": {
  "nls": "index.js"
},
```
next go back to your index.js and add the shebang to make it executable. Note the shebang should be the first line in your js file.

```
#!/usr/bin/node
```
Shebang tells which interpreter to use. We are telling to use node. 

Now in your directory with the package.json run `npm install -g .` (npm link is also a alternative)
This should mean you can now `nls` on the terminal and see something. Something like 

![Node - File List](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zdvau6n8jbjskkqppwo1.png)

Exciting. This is an array of the files and folders in the directory. Wohoo. Almost, almost. Note two important points. This is an array. All files are colored in the same green color. Let's work on fixing that. Let's install chalk to color the console outputs. Chalk is terminal styling helper. It provides a simple wrapper to style/color the console logs of your application. [Chalk](https://www.npmjs.com/package/chalk)

`npm install --save chalk`

Now let's use the options object of the `readdir` function. 
Change the code as

```
readdir(process.cwd(), { withFileTypes: true },...
// no changes here..
)
```
withFileTypes ensures that files that we get back are of class of type `fs.Dirent`. This is node file object which has certain properties and methods which are very usual here. [fs.Dirent](https://nodejs.org/dist/latest-v14.x/docs/api/fs.html#fs_class_fs_dirent). One of this is a method `fs.isDirectory()` that returns a boolean. Like you get from the name. It can be useful to check whether it is directory or not. Let's include that. Modify our callback function as

```
//index.js

readdir(process.cwd(), { withFileTypes: true }, (err, files) => {
	if (err) {
		log(chalk('ERROR'));
	}
	files.forEach((file) => {
		if (file.isDirectory()) {
			log(chalk.blueBright(file.name));
		} else {
			log(chalk.whiteBright(file.name));
		}
	});
});
```
Hmm hmm.. Now let's try running it. Save it and type `nls` into your terminal. You can see that folders are blue colored and files are white colored. Yay. 

One more change that I wanna make is to accept a argument. `ls` can take an argument and list the files and folders in the path. For. eg. in your linux machine. `ls /home/{usrname(replace with your usernam)}` can list the files in that directory. Let's add that. 

How can read the arguments passed in the CLI to your file. We can use `process.argv` value. Note that `process.argv` is an array. The first two values are related to node installation in your system and not much interest to us. Let's accept the third value or `process.argv[2]`. Change the code to 

```

const lsArgs = process.argv[2]

const fileDirectory = lsArgs ? lsArgs : process.cwd();

readdir(fileDirectory, { withFileTypes: true }, (err, files) => {
	if (err) {
		log(chalk('ERROR'));
	}
	files.forEach((file) => {
		if (file.isDirectory()) {
			log(chalk.blueBright(file.name));
		} else {
			log(chalk.whiteBright(file.name));
		}
	});
});

```
That was easy. Take the arg if it's present or use the cwd(). Wohoo. We have something. Another improvment is that we can hide the hidden folders from our listing. We can use regex for this check. Something like

`files = files.filter((file) => !/(^|\/)\.[^\/\.]/g.test(file.name));` 

would work well. 

Well well. We have a CLI application. We can actually deploy this to npm. You can login to npm and run `npm deploy` to get it up there. This has gone too long and I am not using going into deployment here. Adding the whole index.js below for your reference. Please let me know your thoughts. 

```
#!/usr/bin/env node

const fs = require('fs');
const chalk = require('chalk');

const { log } = console;
const { readdir } = fs;

const lsArgs = process.argv[2];

const fileDirectory = lsArgs ? lsArgs : process.cwd();

readdir(fileDirectory, { withFileTypes: true }, (err, files) => {
	files = files.filter((item) => !/(^|\/)\.[^\/\.]/g.test(item.name));
	if (err) {
		log(chalk.red('ERROR'));
                return;
	}
	files.forEach((file) => {
		if (file.isDirectory()) {
			log(chalk.blueBright(file.name));
		} else {
			log(chalk.whiteBright(file.name));
		}
	});
});

```