CLI Tools are pretty cool! ✨
They give you a very "hacky" feel , but what is also really cool about CLI tools is that
- They're fast and functional.
- 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 ☀️
- What are APIs? How to use APIs in our Frontend Applications. (Making HTTP requests using axios)
- Object Destructuring (ES6)
- How to build a CLI tool that can be used globally.
Steps involved 🌈
- Initialize a NodeJS Project using
npm init
- Installing necessary packages.
- Interfacing with a Geolocation API that returns our Location Coordinates.
- Interfacing with the Weather API.
- Setting up Inquirer and Boxen.
- Displaying Weather Details on the Terminal.
The Github Repo
Let's get started!
Setting up NodeJS and installing necessary packages.
To begin , create a project directory.
> mkdir weather-cli
cd
into this project directory.
> cd weather-cli
Run
> npm init -y
to setup a NodeJS project.
Let's install the necessary dependencies next.
Run
> 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.
> mkdir bin && touch bin/weather.js
Add the following code to your weather.js
file.
#!/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
"main": "bin/weather.js",
Finally , add the following to the end of your package.json
just after dependencies
.
"bin": {
"weather": "./bin/weather.js"
}
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"
}
}
Now , to run your CLI tool , run the following command
> node .
This should display the Hello World message.
> node .
Hello World
To use this package globally ,
you can install it using
> 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.
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.
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'
}
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);
}
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
{
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.
> 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
.
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 ⭐️
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);
});
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
- type: list
- message
- name
- 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;
...
}
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
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)