Initialization

cd biller npm init -y

npm install chalk

npm install chalk

index.js

#!/usr/bin/env node console.log('The biller app')

#!

0x23

0x21

/usr/bin/env

{ ... "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "bin": { "biller": "./index.js" }, "keywords": [], ... }

bin

npm link

npm link

biller

The biller app

Building the CLI

src

export function cli(args) { console.log(args); }

cli

#!/usr/bin/env node // modify require to use esm which allows ES6 imports. require = require('esm')(module); // import the cli function from cli.js require('./src/cli').cli(process.argv);

esm

cli

cli

process.argv

biller

biller help

process.argv

biller bill --id 2 amount 100

process.argv

cli

// cli.js function parseArguments(args) { args.splice(0, 2); const operation = { type: args[0] } args.forEach(arg => { if (arg.includes('--id')) { const userId = Number(args[args.indexOf(arg) + 1]); if (Number.isNaN(userId)) { throw new Error('Item after `id` must be a number'); } operation.userId = userId; } if (arg.includes('--amount')) { const amount = Number(args[args.indexOf(arg) + 1]); if (Number.isNaN(amount)) { throw new Error('Item after `amount` must be a number'); } operation.amount = amount; } }); if (!(operation.hasOwnProperty('userId') && operation.hasOwnProperty('amount'))) { throw new Error('Must specify userId and amount via --id `id` and --amount `amount`') } return operation }

process.argv

parseArguments

operation.type

biller bill --id 2 --amount 100

args[0]

--id

--amount

--id

--id

--id

operation.userId

--amount

parseArguments

userId

parseArguments

cli

// cli.js export function cli(args) { const operation = parseArguments(args); console.log(operation); }

[ { "id":1, "name":"James John James", "balance":200000, "canWithdraw":true }, { "id":2, "name":"Bosun Egberinde", "balance":500, "canWithdraw":true } ]

cli

cli

const store = require('./store.json');

cli

import * as fs from 'fs'; export function cli(args) { // parse arguments const operation = parseArguments(args); // load from store const store = require('./store.json') // perform billing if (operation.type === 'bill') { const currentUser = store.find(item => item.id === Number(operation.userId)); // check if user exists if (!currentUser) { throw new Error(`No user with id ${operation.userId}`) } // check if user can be billed if (currentUser.canWithdraw && currentUser.balance >= Number(operation.amount)) { // update user balance currentUser.balance -= operation.amount; currentUser.canWithdraw = !(currentUser.amount === 0); // update store with new user data to reflect the current amount fs.writeFile(`${__dirname}/store.json`, JSON.stringify(store), function(err) { if (err) { throw new Error(err.message); } console.log(`Successfully billed ${currentUser.name} ${operation.amount}`); }) } else { throw new Error(`Unable to bill ${currentUser.name}`) } } }

fs

store

cli

canWithdraw

true

store

fs.writeFile

chalk

fs

import chalk from 'chalk';

console.log(chalk.greenBright(`Successfully billed ${currentUser.name} ${operation.amount}`));

#!/usr/bin/env node // modify require to use esm which allows ES6 imports. require = require('esm')(module); const chalk = require('chalk'); try { // import the cli function from cli.js require('./src/cli').cli(process.argv); } catch(error) { console.log(chalk.redBright(`Error: ${error.message}`)) }

cli

Congratulations!!!

In this tutorial, we are going to be building a CLI application from scratch in JavaScript. To follow in this tutorial, you need to have node.js installed on your machine since the JavaScript code we will be writing will not be executed on the browser. You will also need some basic knowledge of Javascript to continue this tutorial. So, let’s get started. We will be building a simple billing application.Create a new folder called biller. From your terminal, move into the directory and initialize npm by running these commands:This tutorial will also use ES6 modules, so we would need to install ESM to take care of that for us.Create a new file:in the project’s root directory. This will act as the entry route of our application. Add the following to index.js.On line 1 is something called a shebang. Theis a human-readable instance of a magic number consisting of the byte string, which is used by the exec() family of functions to determine whether the file to be executed is a script or a binary. Or the shebang is a way of telling UNIX-like systems to use the node interpreter located atin executing the file. This will also make npm run the code using the node executable. To run this code, modify the package.json file to include the following:What to take note of here is thefield. What this does is on install, it creates a symbolic link for index.js — which was specified on line 7 above, to be accessed globally (global installation) or from ./node_modules/bin if the installation wasn’t global. To use this locally, run:If you monitor the output from your terminal whileis running, you will see information regarding the symbolic link that is being created. When that is done, we can run the app on the terminal runningYour terminal should outputIf you have an error, ensure that the shebang is on the first line. If you did not add the shebang, nothing will be outputted on the screen. Congratulations!!! You have built your first CLI application. Now let’s add some functionality to our billing app.Create anfolder. This folder will house all the logic for our billing application. In the src folder, create afile. Add the following toWe are exporting a function called. This function will be imported inwhich was created earlier on. Modifyto look like:On, we are reassigning require to useinstead. Thefunction is imported on. We are passing process.argv to thefunction.contains all the arguments that will be passed when running the app from the terminal. On your terminal, run:to see the content of process.argv. It will output something like:Try running this instead:It will output:As we can see, adding more flags when running biller willthem to thearray. We want to have something like:Hence, we need to be able to parse the contents ofto extract just what we need. We’ll create a new function into handle the parsing. Add the following tounderneath thefunction.There’s a lot happening here since we are doing some validation as well, but it’s nothing too complex. On line 3, we are removing the first two elements in thearray which is what will be received by thefunction. We can do this because we are sure that the first 2 elements are notand we don’t need them. On line 5, we are settingto be the first element of the array. If biller was run with the following arguments:will be. On line 7, we are looping through the args array to get all other arguments. Remember that we are only concerned withandso we will ignore all other arguments. Fromto, we are checking ifwas sent as a part of the argument, and we get the item passed after it. The item passed aftershould be a number representing the user id. We havein place to ensure that it is a number. If it is not a number, we throw an exception with the appropriate message. If the user passed the correct value after, then the number will be stored as. A similar thing is done forin fromto. After parsing the arguments, ourfunction will return an object containing the type of operation that is being carried out, thefor which the operation is to be carried upon, and the amount. Let’s use ourfunction in thefunction.Running biller will output:Now that we can parse the arguments correctly, we need to have a store for our user data. We will store the user data in a JSON file, and update it as required. In thefolder, create afile with the following content:We can now fetch the content of store.json in ourfunction. Modify thefunction to fetch the content of store.json by adding the following to the end of the function.After fetching the contents of store.json, we can now implement the billing functionality. Modify thefunction to look like:We importedin line 1 since we will be writing the updated contents ofto. The new additions to thefunction starts from. First, we check the type of operation to be carried out. If it is a billing operation, we proceed with the billing. Currently, no other operation is supported. Next, we check if there is any user with the id that was passed as an argument. If no user is found, we throw an exception. If a user is found, we check if we can bill the user. To bill the user, the current user balance must be greater than or equal to the amount to be billed, and theproperty of the user must be. If this check fails, we throw an exception on. If the user is billable, we bill the user — reduce his balance, and set his withdrawal status. Next, we update thewith the new user details, using. We throw an exception if something wrong or unexpected happened while updating. If this is successful, we print a success message to the user.Great! Our biller is working correctly. However, success messages should be coloured with a great colour say green, and we are currently throwing exceptions in our application which will break the app. We can, however, handle the exceptions in a good way. First, let’s change the colour of our success messages. Importin. Add the following just below the import forUpdate the console.log statement to:Running biller will now output:Now to handle exceptions, modify index.js to look like:We wrap the call toin ablock. This way, we can catch all exceptions and just output the error message to the user.You have been successfully built a CLI application from scratch. If you had issues while going through this tutorial, leave a comment below and I will make sure to respond appropriately. The source code for this can be found here