DEV Community

Cover image for Build a Weather CLI Tool using NodeJS , Inquirer and WeatherAPI
Ritik Ambadi
Ritik Ambadi

Posted on • Edited on

Build a Weather CLI Tool using NodeJS , Inquirer and WeatherAPI

CLI Tools are pretty cool! ✨

They give you a very "hacky" feel , but what is also really cool about CLI tools is that

  1. They're fast and functional.
  2. They're really easy to build

Today we're going to build a Weather CLI Tool using an npm package called Inquirer. We're also going to use another package - Boxen to draw boxes in our Terminal and finally axios to make HTTP requests to Third-party APIs.

What you'll learn in this project ☀️

  1. What are APIs? How to use APIs in our Frontend Applications. (Making HTTP requests using axios)
  2. Object Destructuring (ES6)
  3. How to build a CLI tool that can be used globally.

Steps involved 🌈

  1. Initialize a NodeJS Project using npm init
  2. Installing necessary packages.
  3. Interfacing with a Geolocation API that returns our Location Coordinates.
  4. Interfacing with the Weather API.
  5. Setting up Inquirer and Boxen.
  6. Displaying Weather Details on the Terminal.

The Github Repo

Github Repo

Let's get started!

Setting up NodeJS and installing necessary packages.

To begin , create a project directory.

> mkdir weather-cli
Enter fullscreen mode Exit fullscreen mode

cd into this project directory.

> cd weather-cli
Enter fullscreen mode Exit fullscreen mode

Run

> npm init -y
Enter fullscreen mode Exit fullscreen mode

to setup a NodeJS project.

Let's install the necessary dependencies next.
Run

> npm i axios boxen inquirer
Enter fullscreen mode Exit fullscreen mode

Set up CLI tool to run globally 🌈

Create a folder with a file called weather.js in our project directory. We'll write all our code in this file.

> mkdir bin && touch bin/weather.js
Enter fullscreen mode Exit fullscreen mode

Add the following code to your weather.js file.

#!/usr/bin/env node

console.log("Hello World");
Enter fullscreen mode Exit fullscreen mode

The line #!/usr/bin/env node is used to tell our system that this file is a node-commandline script.

Next ,
in your package.json file , change "main": "index.js", to

"main": "bin/weather.js",
Enter fullscreen mode Exit fullscreen mode

Finally , add the following to the end of your package.json just after dependencies.

 "bin": {
    "weather": "./bin/weather.js"
  }
Enter fullscreen mode Exit fullscreen mode

Finally your package.json should look like this.

{
  "name": "weather-cli",
  "version": "1.0.0",
  "description": "",
  "main": "bin/weather.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "boxen": "^4.2.0",
    "chalk": "^4.1.0",
    "inquirer": "^7.3.2"
  },
  "bin": {
    "weather": "./bin/weather.js"
  }
}

Enter fullscreen mode Exit fullscreen mode

Now , to run your CLI tool , run the following command

> node .
Enter fullscreen mode Exit fullscreen mode

This should display the Hello World message.

> node .
 Hello World
Enter fullscreen mode Exit fullscreen mode

To use this package globally ,
you can install it using

> npm install -g .
Enter fullscreen mode Exit fullscreen mode

Let's however do this at the final stage.

What is an API? 🌈

An API is basically a kind of a "Menu-card" for Client-side applications.
Backend applications can be thought of as the "chef" that can cook up dishes.

Let's take this analogy , and talk in terms of data.

Backend services can provide different kinds of data.

Web Applications provide an API (Menu-card) for developers like us so that we can make applications off of their data.

Web Applications typically do this to avoid developers from scraping data off of their pages and overloading the page or to charge developers upfront.

The Physical Manifestation of an API

An API is simply a URL to which we make HTTP requests ,

eg. https://api.getweather/current.json?q=London

By making an HTTP GET request to this URL , we can get the Data that this URL provides in the form of JSON/XML.

Let's break down the URL that I've defined above.

https://api.getweather/current.json is our base-uri

If we want the current weather for London , after our base-uri , we append a ? and then our query-string or params in the form of ?key=value pairs.

If we want the current weather for London for the next 7 days , we can chain our params using &. i.e

https://api.getweather/current.json?q=London&days=7

Why do we need a Geolocation API?

In order to request for Weather Data from our WeatherAPI , we need a set of our Location Coordinates (Latitude , Longitude).

Luckily , we can obtain our Location Coordinates using our IP Address.

Making a GET Request to our Geolocation API using axios.

Add the following code to your weather.js file.

const axios = require('axios');
const GL_API = 'http://ip-api.com/json/';

const getGeoLocation = async () => {
    const response = await axios.get(GL_API);
    return response.data;
}
Enter fullscreen mode Exit fullscreen mode

In the code above , I have declared the GL_API constant which is the base-uri for our Geolocation API. I have also required axios and set it to const axios.

You can read more about this API here.

I have added an async function called getGeoLocation. This is because axios.method_name returns a promise that we can easily handle using await.

Let's look at the data that's returned.

{
  status: 'success',
  country: 'India',
  countryCode: 'IN',
  region: 'TG',
  regionName: 'Telangana',
  city: 'Hyderabad',
  zip: '500003',
  lat: 12.346,
  lon: 43.4574,
  timezone: 'Asia/Kolkata',
  isp: 'BSNL Internet',
  org: '',
  as: 'AS9829 National Internet Backbone'
}
Enter fullscreen mode Exit fullscreen mode

We return response.data for our getWeather function that we will define shortly.

You can console.log(response.data) to see if you're getting your location's coordinates. Don't forget to invoke your function when you're testing.

Interfacing with our WeatherAPI

Before writing any code ,

Go to https://www.weatherapi.com and Create an Account.

We need to do this to get an API_KEY.

After Registering and Logging in , you should see your key on the profile page.

Writing the getWeather function

const axios = require('axios');

const GL_API = 'http://ip-api.com/json/';
const W_API = 'http://api.weatherapi.com/v1/current.json?key=<your_api_key>&q='

const getGeoLocation = async () => {
    const response = await axios.get(GL_API);
    return response.data;
}


const getWeather = async() => {
    const {lat , lon} = await getGeoLocation();
    const WeatherURI = W_API + lat + ',' + lon;
    const response = await axios.get(WeatherURI);
    const {temp_c,condition:{text}} = response.data.current;
    console.log(temp_c,text);
}

Enter fullscreen mode Exit fullscreen mode

Let's analyse the getWeather function.

We destructure the lat and lon from the response.data that our getGeoLocationfunction returns.

We construct const WeatherURI in the format below

*W_API(base_uri) + lat(Latitude) + comma(,) + lon(Longitude) *

The URI then becomes ,

http://api.weatherapi.com/v1/current.json?key=<api_key>&q=<lat>,<lon>

We make a GET request to the newly constructed WeatherURI. We store the response in a const.

Let's log the response.data

{
  location: {
    name: 'Hyderabad-Deccan',
    region: 'Andhra Pradesh',
    country: 'India',
    lat: 14.28,
    lon: 41.46,
    tz_id: 'Asia/Kolkata',
    localtime_epoch: 1595516510,
    localtime: '2020-07-23 20:31'
  },
  current: {
    last_updated_epoch: 1595516445,
    last_updated: '2020-07-23 20:30',
    temp_c: 24,
    temp_f: 75.2,
    is_day: 0,
    condition: {
      text: 'Mist',
      icon: '//cdn.weatherapi.com/weather/64x64/night/143.png',
      code: 1030
    },
    wind_mph: 0,
    wind_kph: 0,
    wind_degree: 289,
    wind_dir: 'WNW',
    pressure_mb: 1009,
    pressure_in: 30.3,
    precip_mm: 0,
    precip_in: 0,
    humidity: 94,
    cloud: 75,
    feelslike_c: 25.8,
    feelslike_f: 78.5,
    vis_km: 5,
    vis_miles: 3,
    uv: 1,
    gust_mph: 6.3,
    gust_kph: 10.1
  }
}

Enter fullscreen mode Exit fullscreen mode

Let's destructure temp_c (current.temp_c) and text (current.condition.text). I've used the Object Destructuring syntax here again.

You can now call the getWeather function and you'll now see the temperature and a description of the weather of your location.

> node .
24 Mist
Enter fullscreen mode Exit fullscreen mode

There's a simple weather tool!

Let's add some more functionality to our Weather application now using Inquirer.

Modify the getWeather function to return response.data.current.

const getWeather = async() => {
    const {lat , lon} = await getGeoLocation();
    const WeatherURI = W_API + lat + ',' + lon;
    const response = await axios.get(WeatherURI);
    // const {temp_c,condition:{text}} = response.data.current;
    return response.data.current;
}

Enter fullscreen mode Exit fullscreen mode

Inquirer

Inquirer lets you add prompts to your CLI Tool. You can present questions to the user and then take input from them to execute code accordingly.

You can read about inquirer here ⭐️

Add the following code to weather.js

inquirer
    .prompt([
        {
            type: 'list',
            message: 'Select an option ☀️',
            name: 'source',
            choices: [
                'Temperature in Celsius',
                'Temperature in Farenheit',
                'Wind data',
                'Precipitation',
                'UV Index'
            ],
        },
    ])
    .then(async (answers) => {
        console.log('\n');
        const weather =await getWeather();
        switch(answers.source) {
            case 'Temperature in Celsius':
                console.log(boxen(`Weather : ${weather.temp_c} \u02DAC ${weather.condition.text}\n Feels like: ${weather.feelslike_c} \u02DAC`,{padding:1} ))
            break;
            case 'Temperature in Farenheit':
                console.log(boxen(`Weather : ${weather.temp_f} \u02DAF ${weather.condition.text}\n Feels like: ${weather.feelslike_f} \u02DAF`,{padding:1} ))
            break;
            case 'Wind data':
            console.log(boxen(`Wind Speed: ${weather.wind_mph}mph\nWind Degree: ${weather.wind_degree}\nWind Direction: ${weather.wind_dir}`,{padding:1}))
            break;
            case 'Precipitation':
            console.log(boxen(`Precipitation (mm): ${weather.precip_mm}\nHumidity: ${weather.humidity}`,{padding:1}))
            break;
            case 'UV Index':
            console.log(boxen(`UV: ${weather.uv}\nCloud: ${weather.cloud}`,{padding:1}))
            break;
            default:
            console.error('Invalid Option!')
            break;
        }
    })
    .catch(err => {
        console.error(err);
    });

Enter fullscreen mode Exit fullscreen mode

The code above might look complicated but isn't.
inquirer provides a .prompt method that takes in a few parameters.

We provide the following parameters

  1. type: list
  2. message
  3. name
  4. the choices (array) that you want to be presented on the screen

.prompt returns a promise.
You can handle the promise with a .then
We define an async callback function.

This callback function provides a variable that we can use. We've defined the parameter as answers. We destructure source from the answers variable.

We then use a switch-case construct to handle different user's choices.

switch(answers.source) {
 case 1: 
 statement
 break;
 ...
}
Enter fullscreen mode Exit fullscreen mode

We provide the data that the user needs based on the user's decision by using the weather constant.

const weather = getWeather();

And with this , we've just built our very own Weather CLI tool! 🎉

You can now install this tool globally for personal use.

> sudo npm install -g .
weather
Enter fullscreen mode Exit fullscreen mode

That's all folks! 👨🏽‍💻🌈

You can make your own changes to this project and once you feel like the world is ready to use your awesome project ,

You can read this article from Zell , which details over how you can publish your packages on npm.

Top comments (0)