Build a Weather CLI Tool using NodeJS , Inquirer and WeatherAPI
Published: 2020-07-23 16:30:42

**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](https://ip-api.com) that returns our Location Coordinates.
4. Interfacing with the [Weather API](https://www.weatherapi.com).
5. Setting up Inquirer and Boxen.
6. Displaying Weather Details on the Terminal.

#### The Github Repo
[Github Repo](https://github.com/ARitik/Weather-cli)

Let's get started!

##Setting up NodeJS and installing necessary packages.
To begin , create a project directory. 

```Bash
> mkdir weather-cli
```

`cd` into this project directory.

```Bash
> cd weather-cli
```

Run
 ```Bash
> npm init -y
``` 
to setup a NodeJS project. 

Let's install the necessary dependencies next. 
Run 
```Bash
> npm i axios boxen inquirer
```

## 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.

```Bash
> mkdir bin && touch bin/weather.js
```

Add the following code to your `weather.js` file.

```Javascript
#!/usr/bin/env node

console.log("Hello World");
```

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 
```JSON
"main": "bin/weather.js",
```
Finally , add the following to the end of your `package.json` just after `dependencies`.
```JSON
 "bin": {
    "weather": "./bin/weather.js"
  }
```

Finally your `package.json` should look like this.

```JSON
{
  "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"
  }
}

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

```Bash
> node .
```
This should display the _Hello World_ message. 

```Bash
> node .
 Hello World
```

To use this package globally , 
you can install it using

```Bash
> npm install -g .
```

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.

```Javascript
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;
}
```
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](https://ip-api.com).

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.

```JSON
{
  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'
}
```

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]('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

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

```


Let's analyse the `getWeather` function.

We destructure the `lat` and `lon` from the `response.data` that our `getGeoLocation`function 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` 

```JSON
{
  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
  }
}

```
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. 

```Bash
> node .
24 Mist
```
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`.

```Javascript
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;
}

```


#### 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](https://www.npmjs.com/package/inquirer) ⭐️

Add the following code to `weather.js`

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

```

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. 

```Javascript
switch(answers.source) {
 case 1: 
 statement
 break;
 ...
}
```
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.

```Bash
> sudo npm install -g .
weather
```


#### 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](https://zellwk.com/blog/publish-to-npm/) from Zell , which details over how you can publish your packages on npm.