Skip to main content

Building A Node.js Based CLI For Real-Time COVID-19 Vaccination Tracking

Why Build?

As we already know the whole world is suffering from COVID-19 and the vaccinations are going in full swing everywhere. Finding a slot is getting tougher in our country India as we have a huge population to be vaccinated. Numerous times we have to go to CoWin site to search for a slot and slots are always full. It is pretty time-consuming and irritating. Being a developer, I thought most of the time is usually spent by us in the terminal so why can't we have a basic terminal-based app to save time. So this post will help you in two ways

  •    Learn how to create Node.js based CLI's
  •    Get real-time info about vaccination slots for your area.

Let's begin our initial setup!

Pre-requisite – We are assuming you have installed Node.js and npm, If not you can install from here

So as a first step lets initialize our project using command

npm init

Enter the basic details as shown below.

This will create package.json file in the folder cowinCLI. The next step is to create a bin folder that will have our index.js file containing our application.


Open the index.js file and add the below-mentioned first line. This actually tells the interpreter that whatever code runs below this will be handled by the node.

#! /usr/bin/env node

If you remember while creating we have mentioned our entry point as index.js but actually, this file now exists in the bin folder. So we will correct that as well as we will add one more entry. The new entry which we will add is for the keyword we want to use to call our CLI. We want to use something like cowin. So we will add this entry.

"bin": {

    "cowin": "./bin/index.js"

  }

So your package.json will look something like this

{

  "name": "vaccli",

  "version": "1.0.0",

  "description": "CLI vaccination slots",

  "main": "bin/index.js",

  "scripts": {

    "test": "echo \"Error: no test specified\" && exit 1"

  },

  "author": "Nabheet",

  "license": "ISC",

  "dependencies": {

    "axios": "^0.21.1",

    "chalk": "^4.1.1",

    "commander": "^7.2.0",

    "inquirer": "^8.0.0",

    "node-notifier": "^9.0.1",

    "tty-table": "^4.1.3"

  },

  "bin": {

    "cowin": "./bin/index.js"

  }

}

So the basic structure is set. Now before we start adding the functionality we have not given a thought to how we will fetch the data? Let's first check that.

 

Do we have an API to fetch covid vaccination slots data?

Thank God for looking at Co-Win site they have provided us with OpenAPI. 50% of work is done. Now, all we need to do is, consume this data and work as per our need. Let's now think about what our CLI will do.


What functions our CLI will perform?

On looking closely at the calendar slots api for a district( In India we have a Country comprised of States and Union Territories which in turn consists of districts) we can see it needs some kind of district id.

So looking at how do we get districts id we found another api but that needs state id

How do we get state id's another API😊

So our CLI shall do the following.

  • Ability to get all states and id's
  • Ability to get all district id's for a state id
  • Ability to get slots by district id
  • Ability to filter slots by ages as we have slots by 18-45 and 45 and above.
  • Apart from this some beautification
  • Desktop notification

In order to achieve this, we will be using multiple npm modules lets install them first using the below-mentioned command

 

npm install axios chalk commander inquirer node-notifier tty-table

Packages to be installed

  • Axios –for calling the different API's
  • Chalk – for beautifying the console output
  • Commander – giving the different options and command in CLI such as cowin states or cowin districts <state id>;
  • Inquirer – for getting user input for entering the age filter
  • Node-notifier – send desktop notification
  • Tty-table – format our table output

Let's begin by creating separate functions for them. Create a util folder under the cowinCLI project. Create files states.js, districts.js, config.js, and slots.js in util folder. Config.js is for the configuration related common data such as table header formatting which will be used by all functions

 

// Common configuration data to be used by all functions.

exports.config = {

  headers: { "User-Agent": "Axios - console app" },

};

exports.options = {

  borderStyle: "solid",

  borderColor: "blue",

  headerAlign: "center",

  align: "left",

  color: "white",

  truncate: "...",

  width: "90%",

};

Let's first code our reusable States function in states.js

If you look, we need to call the states API for that we will use our already installed npm package axios. We are calling the API and once we got a response we are formatting the table data by using the tty-table package and writing the output to the console. So this function will return the formatted output of states and its id's.

 

const axios = require('axios');

const table = require("tty-table");

const { config,options } = require('./config');

// function to return list of all states

module.exports = function() {

    axios

    .get("https://cdn-api.co-vin.in/api/v2/admin/location/states", config)

    .then((response) => {

      // table formatter

      let header = [

        {

          value: "state_id",

          headerColor: "cyan",

          alias: "State ID",

          color: "white",

          align: "left",

          width: 40,

        },

        {

          value: "state_name",

          alias: "State",

          headerColor: "cyan",

          color: "white",

          align: "left",

          width: 40,

        },

      ];

      const out = table(header, response.data.states, options).render();

      console.table(out);

    })

    .catch((error) => {

      console.log(error);

    });

};

Let's code our second reusable Districts function in districts.js

For this also we will use a similar set up of axios and tty-table. The only thing to be noted is, in this function which we are exporting has an argument as stateid.

 

const axios = require('axios');

const table = require("tty-table");

const { config,options } = require('./config');

// Function which take stateid as input and return all the formatted districts

module.exports = function(stateid) {

    axios

    .get(

      `https://cdn-api.co-vin.in/api/v2/admin/location/districts/${stateid}`,

      config

    )

    .then((response) => {

      // Table header specific formatting

      let header = [

        {

          value: "district_id",

          headerColor: "cyan",

          alias: "District ID",

          color: "white",

          align: "left",

          width: 40,

        },

        {

          value: "district_name",

          alias: "District",

          headerColor: "cyan",

          color: "white",

          align: "left",

          width: 40,

        },

      ];

      // Output the results.

      const out = table(header, response.data.districts, options).render();

      console.table(out);

    })

    .catch((error) => {

      console.log(error);

    });

};

Let's code our third reusable slots function in slots.js

For this also we will use a similar set up of axios and tty-table. The only thing to be noted is in this function which we are exporting has an argument as districtid. In addition to it you can see we are using chalk and inquirer package. Chalk is used to format the headers above the table and inquirer is used for taking input from user when slots command is run. We have also used node-notifier which will send desktop notification as soon as it runs, just an example. You can modify this behavior to code your own custom logic.

 

const axios = require('axios');

const table = require("tty-table");

const chalk = require("chalk");

const notifier = require("node-notifier");

var inquirer = require("inquirer");

const { config,options } = require('./config');

 

// function to check slots.

module.exports = function(district) {

  //Input prompt for getting what age you want to check records.

    inquirer

    .prompt([

      {

        type: "list",

        name: "choice",

        message: "Which Age group?",

        choices: [

          {

            name: "View All",

            value: "",

          },

          {

            name: "45 Plus",

            value: "45",

          },

          {

            name: "18 - 45 ",

            value: "18",

          },

        ],

      },

    ])

    .then((answers) => {

      const date = new Date();

      var todaysDate = `${date.getDate()}-${String(

        date.getMonth() + 1

      ).padStart(2, "0")}-${date.getFullYear()}`;

      console.log(

        chalk.underline.bgRed.bold(`Showing Slots from - ${todaysDate}`)

      );

     

      axios

        .get(

          `https://cdn-api.co-vin.in/api/v2/appointment/sessions/public/calendarByDistrict?district_id=${district}&date=${todaysDate}`,

          config

        )

        .then((response) => {

          let finalData = [];

          let districtName;

 

          response.data.centers.forEach((item) => {

            item.sessions.forEach((session) => {

              districtName = item.district_name;

              // based on user age choice filter the data

              if (answers.choice == "") {

              

                let data = {

                  Center: item.name,

                  Address: item.address,

                  Date: session.date,

                  FreeSlots: session.available_capacity,

                  Age: session.min_age_limit,

                };

                finalData.push(data);

              } else if (

                answers.choice == "18" &&

                session.min_age_limit == "18"

              ) {

               

                let data = {

                  Center: item.name,

                  Address: item.address,

                  Date: session.date,

                  FreeSlots: session.available_capacity,

                  Age: session.min_age_limit,

                };

                finalData.push(data);

              } else if (

                answers.choice == "45" &&

                session.min_age_limit == "45"

              ) {

              

                let data = {

                  Center: item.name,

                  Address: item.address,

                  Date: session.date,

                  FreeSlots: session.available_capacity,

                  Age: session.min_age_limit,

                };

                finalData.push(data);

              }

            });

          });

          console.log(

            chalk.underline.bgGreen.bold(`District - ${districtName}`)

          );

          switch (answers.choice) {

            case "":

              console.log(chalk.underline.bgBlue.bold(`All ages`));

              break;

            case "45":

              console.log(chalk.underline.bgBlue.bold(`45+ Age`));

              break;

            case "18":

              console.log(chalk.underline.bgBlue.bold(`18-45 Age`));

              break;

            default:

              break;

          }

          // table formatting

          let header = [

            {

              value: "Center",

              headerColor: "cyan",

              color: "white",

              align: "left",

              width: 40,

            },

            {

              value: "Address",

              headerColor: "cyan",

              color: "white",

              align: "left",

              width: 40,

            },

            {

              value: "Date",

              headerColor: "cyan",

              color: "white",

              align: "left",

              width: 15,

            },

            {

              value: "FreeSlots",

              headerColor: "cyan",

              color: "white",

              align: "left",

              width: 20,

            },

            {

              value: "Age",

              headerColor: "cyan",

              color: "white",

              align: "left",

              width: 20,

            },

          ];

          const out = table(header, finalData, options).render();

          console.table(out);

          notifier.notify({

            title: "Vaccination Slots Available",

            subtitle: "Daily Maintenance",

            message: "Immediately go and check Vaccination slots!",

            wait: true,

          });

        })

        .catch((error) => {

          console.log(error);

        });

    })

    .catch((error) => {

      if (error.isTtyError) {

        // Prompt couldn't be rendered in the current environment

      } else {

        // Something else went wrong

      }

    });

};

Now our all basic functions are in place but what is pending is the actual CLI😊 Let's start building that.

 

Lets now build the CLI by updating index.js

So far we have used all npm packages except commander it is the heart of our CLI. We will be using commander for making the subcommands as well as flag options. As can be seen, below we have used both command and option. Commands for getting states, districts, and slots and they have a callback function mentioned as our reusable functions under action.

#! /usr/bin/env node

 

 

const program = require("commander");

// import all functions

const districts = require('../util/districts');

const states = require('../util/states');

const slots = require('../util/slots');

 

 

 

// adding different cli options,commands and passing callback functions in actions

 

program.option("-a, --available", "Output If slots available");

program

  .command("states")

  .description("Get all State Codes and descriptions.")

  .action(states);

program

  .command("district <stateid>")

  .description("Get all district of a State")

  .action(districts);

 

program

  .command("slots <districtid>")

  .description("Get slots for the district")

  .action(slots);

 

program.parse();

Final touches

So we have everything ready all we need to do is now run the below command which will install our package globally.

npm install -g .

Output

 

cowin states


cowin districts 12


cowin slots 187



 

Comments

Popular posts from this blog

Organizing Data In Table: A Quick Guide

  Organizing Data In Table: A Quick Guide We can use tables to structure data in columns and rows. The table is the HTML way to lay out the data. The CSS way to create the layout on the web page is  CSS float ,  flexbox , and  CSS grid . We cover an example to understand how to create a table on the web page. You can view the HTML table example at the below codepen link: https://codepen.io/taimoorsattar/pen/NWpdwbp For example, we can create a table in HTML for customer’s grocery item bill as below: < table border = "3" cellpadding = "10" cellspacing = "0" > < caption > Grocery Items Bill </ caption > < thead > < colgroup > < col width = "60%" > < col width = "20%" > < col width = "20%" span = "1" style = "background-color:#f1f1f1;" > </ colgroup > < tr > < th align = ...

Detecting The User's Color Scheme Preference With CSS

Detecting The User's Color Scheme Preference With CSS If you’re a developer, chances are that you use dark mode on your machine and code editor. If not, what are you waiting for? Join the dark side! Jokes apart, it is common nowadays to allow users to select a different theme when visiting a website. Now you can do this with CSS only, not the theme selection itself, for that you still need JS but with CSS you can now detect the user’s machine color scheme (light or dark) and display the correct colors on your website immediately. To do this we need to use the CSS variables. According to the website  Can I use , the “CSS variables” feature is available on 95% of the currently used browsers around the world. We also need to use the  prefers-color-scheme  media query, which according to  Can I use  is supported by about 90% of the currently used browsers. In this article, I will show you how to use the CSS variables and the  prefers-color-scheme  to setup...

5 CSS Tips and Tricks to Try in Your Next Project

  5 CSS Tips and Tricks to Try in Your Next Project Looking for inspiration on how to add a twist to your project design? Take a look at these 5 CSS techniques and have fun experimenting with some bold ideas! 1. Bring back the 90’s with the background-clip Have you ever wondered how to apply a gradient or a texture to the text in CSS? The good news is you can easily achieve that with the background-clip property! First, we need to apply the background color to our  <h1> , then use the value text for the  background-clip  property and set the text color to transparent. < h1 class = "wordart" > The background is clipped to this text </ h1 > h1 { background-color: #ff1493; background-image: linear-gradient(319deg, #ff1493 0%, #0000ff 37%, #ff8c00 100%); } .wordart { -webkit-background-clip: text; color: transparent; } And voilà, the 90’ style WordArt is ready! 2. Crazy shapes with clip-path If you like to experiment with your ...