DEV Community

Cover image for Let's build a simple Node.js CLI application
vishnu prasad
vishnu prasad

Posted on

Let's build a simple Node.js CLI application

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.

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. 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
2) Synchronous readdir. Doc
3) promises based one. Doc

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 if you are interested. )

const fs = require('fs')
Enter fullscreen mode Exit fullscreen mode

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)
})
Enter fullscreen mode Exit fullscreen mode

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"
},
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

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

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..
)
Enter fullscreen mode Exit fullscreen mode

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. 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));
        }
    });
});
Enter fullscreen mode Exit fullscreen mode

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));
        }
    });
});

Enter fullscreen mode Exit fullscreen mode

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));
        }
    });
});

Enter fullscreen mode Exit fullscreen mode

Discussion (0)