You may have heard of Command-line applications (CLI) as the core tools for tasks of automating. For instance, running tests, building reports, deploying production applications, DevOps, migrating data, etc. So, if you have to repeat the same things over and over again then you have the option to automate those steps with a script. This will save a lot of your time. In this blog, we will discuss the topic of Node.js request. Because Node.js is a great solution for writing CLI apps. Node.js contains inbuilt libraries that you can use for reading and writing files, basic network communication, and launching other applications. Additionally, there are various packages available on npm for almost most of the imaginable tasks.

Node.js request

Also Read: Node js on hosting: Best Hosting Platforms for 2021

Building first Node.js request Command Line Application

To complete this tutorial you will require the following things:

  • Firstly, most recent version of Node.js downloaded and installed
  • Secondly, a nice text editor like Visual Studio Code.
  • Lastly, you require a free Okta developer account

Now you have to open your computer’s command prompt (Windows) or terminal (macOS/Linux). You also have to change the current directory to that folder where you are saving your projects or documents. After doing so, enter the command mentioned below for creating a new project folder and initializing the project.

mkdir hello-cli
cd hello-cli
npm init

You have to open the hello-cli folder in your text editor now. After that, create a folder called bin and add a new file to that folder that goes by the name of index.js. Then open the index.js file in your favorite text editor and write the command mentioned below.

#!/usr/bin/env node

console.log( "Hello!" );

In the above command, you can see that the first line is beginning with #! what is usually known as a “shebang.” You can use this normally on Linux or UNIX operating systems only. It is used for informing the system of the type of script included in the rest of the text file.

Now you have to open the package.json file in the root of the project in the text editor. Also, change the main value to bin/index.js. Along with the text mentioned below, you also have to add a new key for bin .

"bin": {
   "hello": "./bin/index.js"
 }

Your entire package.json file  will look similar to the file mentioned below:

{
 "name": "hello-cli",
 "version": "1.0.0",
 "description": "",
 "main": "bin/index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "keywords": [],
 "author": "David Neal (https://reverentgeek.com)",
 "license": "MIT",
 "bin": {
   "hello": "./bin/index.js"
 }
}

You can run the script like any other Node.js application at this point. So, enter the following command line.

node .

But the basic goal of writing such a script is to be able to run it from anywhere. You can do so with the npm install command.

npm install -g .

Now this will install your script “globally.” All the commands available in the bin section of the package.json file will be accessed as command-line applications. So, now you can run your script by typing hello at the command line!

hello

You have to run the below-mentioned command for uninstalling your script.

npm uninstall -g hello-cli

Making text stand out with Colour and Borders

Your job will be done by writing plain text directly to the console. However, it is nicer or sometimes necessary to have your content stand out. For instance, you can make error messages appear using the color Red. For modifying the color of text and background, we recommend you use chalk. You can add borders around your text for making it more visible by using a module called boxen. So, you can add both of them to your projects.

npm install chalk@2.4 boxen@4.0

Now you have to replace the contents of bin/index.js with the code mentioned below.

#!/usr/bin/env node

const chalk = require("chalk");
const boxen = require("boxen");

const greeting = chalk.white.bold("Hello!");

const boxenOptions = {
 padding: 1,
 margin: 1,
 borderStyle: "round",
 borderColor: "green",
 backgroundColor: "#555555"
};
const msgBox = boxen( greeting, boxenOptions );

console.log(msgBox);

Thereafter, you have to install the updated script and run it.

npm install -g .
hello

Lastly, the message in your console will look similar to the image below.

Add Support for Command Line Arguments

Most of the CLI applications accept one or more command-line arguments. For instance, commands, optional/required parameters, flags/switches, or other configuration values. Even though you have the option to parse command line parameters by inspecting the Node.js process.argv value. It will be better to use the modules available as it will save you a lot of effort and time. The yargs module is an example of such a module for Node.js whose design is to support the most common CLI scenarios.

Firstly, you have to install the yargs module as a dependency for your application.

npm install yargs@13.2

Secondly, you have to update the bin/index.js file with the help of the code mentioned below.

#!/usr/bin/env node

const yargs = require("yargs");

const options = yargs
 .usage("Usage: -n <name>")
 .option("n", { alias: "name", describe: "Your name", type: "string", demandOption: true })
 .argv;

const greeting = `Hello, ${options.name}!`;

console.log(greeting);

The previous code will help you in importing the yargs module and configuring it to require one argument for name. Then, install the updated script globally.

npm install -g .

It is necessary to have the name parameter (demandOption: true). So, when you run the hello script the same as before, you will see something similar to the following.

> hello

Usage: -n <name>

Options:
 --help      Show help                                                [boolean]
 --version   Show version number                                      [boolean]
 -n, --name  Your name                                      [string] [required]

Missing required argument: n

The yargs module creates an amazing response for displaying help automatically. The CLI is not only ready for accepting -n and --name arguments but also --help and --version. Now you should try to run your CLI application with any of the following arguments.

> hello -n me

Hello, me!

> hello --version

0.1.0

Call your Node.js request API from the Command-line

There is a common scenario in automating tasks which are calling an API endpoint to get data or to send data to an API endpoint. In this part of our blog, we are going to take a random joke from a joke API and display it in the console. To retrieve and send data to an API in Node.js request, we recommend you use one of the popular libraries called axios. You have to start by adding this library as a dependency.

npm install axios@0.21.1

Now you have to replace the contents of bin/index.js with the below-mentioned code.

#!/usr/bin/env node

const yargs = require("yargs");
const axios = require("axios");

const options = yargs
 .usage("Usage: -n <name>")
 .option("n", { alias: "name", describe: "Your name", type: "string", demandOption: true })
 .argv;

const greeting = `Hello, ${options.name}!`;
console.log(greeting);

console.log("Here's a random joke for you:");

const url = "https://icanhazdadjoke.com/";

axios.get(url, { headers: { Accept: "application/json" } })
 .then(res => {
   console.log(res.data.joke);
 });

Along with responding with a greeting, the CLI application can also now retract a random joke using axios and display it immediately after the greeting.

Adding a Search Argument to Node.js request Command-line Application

We can take the CLI Application one step ahead by supporting a search argument. You have to replace the contents of bin/index.js with the below-mentioned code.

#!/usr/bin/env node

const yargs = require("yargs");
const axios = require("axios");

const options = yargs
 .usage("Usage: -n <name>")
 .option("n", { alias: "name", describe: "Your name", type: "string", demandOption: true })
 .option("s", { alias: "search", describe: "Search term", type: "string" })
 .argv;

const greeting = `Hello, ${options.name}!`;
console.log(greeting);

if (options.search) {
 console.log(`Searching for jokes about ${options.search}...`)
} else {
 console.log("Here's a random joke for you:");
}

// The url depends on searching or not
const url = options.search ? `https://icanhazdadjoke.com/search?term=${escape(options.search)}` : "https://icanhazdadjoke.com/";

axios.get(url, { headers: { Accept: "application/json" } })
 .then(res => {
   if (options.search) {
     // if searching for jokes, loop over the results
     res.data.results.forEach( j => {
       console.log("\n" + j.joke);
     });
     if (res.data.results.length === 0) {
       console.log("no jokes found :'(");
     }
   } else {
     console.log(res.data.joke);
   }
 });

We added support for a new --search the argument in the previous code. The code can use a different URL depending on retracting a random joke or searching for specific jokes. And in the conclusion, it must handle the results differently. You have to try this out!

> npm install -g .
> hello -n me -s orange

Add Support for Secure Authorization with PKCE

An API requiring basic authentication is pretty straightforward. However, the situation changes if an API uses OAuth for authentication which is possible. PKCE i.e., Proof Key for Code Exchange is a nice solution for mobile and native applications for exchanging private keys with an authorization server. We are going to use the Okta API for this purpose. But it must be possible to adapt the code to your work with any OAuth 2.0 service you’re using. You can use Okta free of cost as it simplifies handling user authentication, social login, password reset, authorization, etc. Additionally, it can utilize open standards such as OAuth 2.0 for making integration seamless.

Create an Okta Application

Before beginning, please ensure that you have a free Okta developer account. So, install the Okta CLI and run okta register for signing up for a new account. In case you already have an account then just run okta login. After doing so, run okta apps create. You can select the default app name, or change it as you like. Now choose Native and press Enter.

You can use http://localhost:8080/callback for the Redirect URI and set the Logout Redirect URI to http://localhost:8080 .

Now you have to create a file with the name of .env in the root of your Node.js CLI project. Then, open the file and add the information given below.

OKTA_ORG_URL=https://{yourOktaOrgUrl}
OKTA_CLIENT_ID={yourClientID}
OKTA_SCOPES="openid profile email"
OKTA_REDIRECT_PORT=8080

Update the Node.js request Command-line Application

For supporting the PKCE authentication flow, your CLI application requires a few more libraries. In this blog, we will use hapi creating a webserver to handle the authentication callback. dotenv helps to read configuration settings from the .env file. To launch the default browser for login, you will use open. Additionally, you can use uuid for generating a unique private key to exchange with the authentication server.

npm install @hapi/hapi@18.3 dotenv@8.0 open@6.3 uuid@3.3

Now you have to create a new folder in the root of the project with the name ofsrc. Create a new file in this folder and name it authClient.js. Then, add the below-mentioned code to the src/authClient.js file.

"use strict";

const axios = require( "axios" );
const crypto = require( "crypto" );
const hapi = require( "@hapi/hapi" );
const open = require( "open" );
const querystring = require( "querystring" );
const uuid = require( "uuid/v1" );

const base64url = str => {
 return str.replace( /\+/g, "-" ).replace( /\//g, "_" ).replace( /=+$/, "" );
};
 module.exports = ( { oktaOrgUrl, clientId, scopes, serverPort } ) => {
 if ( !oktaOrgUrl || !clientId || !scopes || !serverPort ) {
   throw new Error( "Okta organization URL, client ID, scopes, and server port are required." );
 }
 // code verifier must be a random string with a minimum of 43 characters
 const codeVerifier = uuid() + uuid();
 const redirectUri = `http://localhost:${serverPort}/callback`;

 const buildAuthorizeUrl = ( codeChallenge ) => {
   const data = {
     client_id: clientId,
     response_type: "code",
     scope: scopes,
     redirect_uri: redirectUri,
     state: uuid(),
     code_challenge_method: "S256",
     code_challenge: codeChallenge
   };
   const params = querystring.stringify( data );
   const authorizeUrl = `${oktaOrgUrl}/oauth2/v1/authorize?${params}`;
   return authorizeUrl;
 };

 const getUserInfo = async accessToken => {
   try {
     const config = {
       headers: { Authorization: `Bearer ${accessToken}` }
     };
     const url = `${oktaOrgUrl}/oauth2/v1/userinfo`;
     const res = await axios.get( url, config );
     return res.data;
   } catch ( err ) {
     console.log( "error getting user info", err ); // eslint-disable-line no-console
     throw err;
   }
 };

 const getToken = async code => {
   try {
     const request = {
       grant_type: "authorization_code",
       redirect_uri: redirectUri,
       client_id: clientId,
       code,
       code_verifier: codeVerifier
     };
     const url = `${oktaOrgUrl}/oauth2/v1/token`;
     const data = querystring.stringify( request );
     const res = await axios.post( url, data );
     return res.data;
   } catch ( err ) {
     console.log( "error getting token", err ); // eslint-disable-line no-console
     throw err;
   }
 };
  // Start server and begin auth flow
 const executeAuthFlow = () => {
   return new Promise( async ( resolve, reject ) => {
     const server = hapi.server( {
       port: serverPort,
       host: "localhost"
     } );

     server.route( {
       method: "GET",
       path: "/callback",
       handler: async request => {
         try {
           const code = request.query.code;
           const token = await getToken( code );
           const userInfo = await getUserInfo( token.access_token );
           resolve( { token, userInfo } );
           return `Thank you, ${userInfo.given_name}. You can close this tab.`;
         } catch ( err ) {
           reject( err );
         } finally {
           server.stop();
         }
       }
     } );
     await server.start();

     const codeChallenge = base64url( crypto.createHash( "sha256" ).update( codeVerifier ).digest( "base64" ) );
     const authorizeUrl = buildAuthorizeUrl( codeChallenge );
     open( authorizeUrl );
   } );
 };
  return {
   executeAuthFlow
 };
};

The module of authClient.js exports one function that accepts an object with the properties oktaOrgUrlclientIdscopes, and serverPort. When you call this function, it will initialize the module with the configuration it requires. After the initialization, the function returns an object with exactly one function, executeAuthFlow.

Node.js request

Now you have to update the CLI for using the authClient.js module. So, create a a new file under bin with the name of  pkceLogin.js. You have to add the below mentioned code to the pkceLogin.js file.

#!/usr/bin/env node
"use strict";

const chalk = require( "chalk" );
const dotenv = require( "dotenv" );
const authClient = require( "../src/authClient" );

// read in settings
dotenv.config();

const config = {
 oktaOrgUrl: process.env.OKTA_ORG_URL,
 clientId: process.env.OKTA_CLIENT_ID,
 scopes: process.env.OKTA_SCOPES,
 serverPort: process.env.OKTA_REDIRECT_PORT
};

const main = async () => {
 try {
   const auth = authClient( config );
   const { token, userInfo } = await auth.executeAuthFlow();
   console.log( token, userInfo );
   console.log( chalk.bold( "You have successfully authenticated your CLI application!" ) );
 } catch ( err ) {
   console.log( chalk.red( err ) );
 }
};

main();

In order to include another command in the bin section, you have to update the package.json file.

"bin": {
   "hello": "./bin/index.js",
   "pkce-login": "./bin/pkceLogin.js"
 },

You have to update the CLI applications that are installed globally.

npm install -g .

You are finally ready to test your new CLI authentication. When you have logged in, you will see in your console,

You have successfully authenticated your CLI application!

pkce-login

Conclusion

We hope that our blog on Node.js request helped you in understanding it. Thank you for reading our blog!