Build a Weather App with Vanilla JS
Tags: webdev, javascript
Published: 2023-04-03 23:29:17

by [Onisile Tobi](https://blog.openreplay.com/authors/onisile-tobi)


In this tutorial, we will walk through the steps involved in creating a simple weather app that displays the current weather data for a given location. We will be using vanilla JavaScript which means we will not be relying on any external libraries or frameworks.
By the end of this tutorial, we will have a fully functional weather app using the OpenWeatherMap API.

![-](https://blog.openreplay.com/images/build-a-weather-app-with-vanilla-js/images/o0lw5nh.png)

The goal of this tutorial is to show how you can create a functional weather app with vanilla JavaScript which makes it easily usable as a standalone page or with any framework you choose.

Here is what we will have built by the end of this tutorial

*below is the final result on pc*


![-](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vkh0ggh9iklzaspr9pvc.png)


*below is the final result on mobile*
![-](https://blog.openreplay.com/images/build-a-weather-app-with-vanilla-js/images/nCTGO0u.png)




## Prerequisites
* An OpenWeatherMap `API` key: go to https://openweathermap.org/api and signup to have your `API` key sent to the email address you provide

* Tailwind CSS framework: go to https://www.unpkg.com/browse/tailwindcss@0.7.4/dist/ and click on `tailwind.min.css`
* Font Awesome icons and css files: go to https://fontawesome.com/download and download the zip file you choose
* Go to https://fonts.google.com/download?family=Fredoka to download the fredoka font-family; convert the bold `font-face` from `ttf` to `woff2` and rename it as `fredoka.woff2` as will be shown in the directory tree below
* A code editor such as VS Code. Alternatively, you can use your Chrome, Brave, or Edge developer tools as an editor by going to the sources tab and dragging your desired folder containing an HTML file into it, and clicking allow.

## Overview of The OpenWeatherMap API
The `API` has a free tier that allows 60 calls in one minute and one million calls monthly that we can use with an `API` key as long as we stay within its limits.

To make our app load the `API` response very fast, we will only make requests to the https://api.openweathermap.org/data/2.5/weather endpoint which returns sufficient but not too much data about the current weather about any city or country available in the `API`


```
https://api.openweathermap.org/data/2.5/weather?q={city or country name}&appid={API key}&mode=xml
```

- the `q` variable stores the city or country name to be requested,
- the `appid` variable stores the unique `API` key provided by you,
- the `mode` variable makes your request for a response type formatted as `XML` or `JSON`, it defaults to `JSON`

For unavailable or non-existent cities, the `API` returns a `string` similar to 
```json
{
  "cod": "404",
  "message": "city not found"
}
``` 
which, when parsed as JSON, can be used to notify users of unavailability.

The general `API` has a lot of features such as data for forecasts, and agriculture among others that you can learn about on their website.

## Creating an OpenWeatherMap Account
In this section, I will shortly discuss how to create or open an OpenWeatherMap account which is required for an `API` key.

- Go to https://openweathermap.org/api and signup or sign in to create your account and get your API key
- Click on the `API` section to get your key - they offer both paid and free services.

## Designing the App Interface
In this section, I will show us all the necessary process that applies in developing the application from using vanilla `JS` to the UI/UX which is done with the `tailwind CSS` framework, and how to set up the `web fonts ` folder below;
### 1. Creating the files folder:
Make a folder with any name you wish (say, `parent-folder`) with a similar directory tree as below:

```
parent-folder
├── fredoka.woff2
├── index.html
├── css
│   └── tailwind.min.css
│   └── all.min.css
|   └── css.css
└── js
|   └── page.js
└── webfonts
```

Both the `webfonts` folder and the `all.min.css` file are present in the zip file downloaded at the Font Awesome website; go to https://fontawesome.com/docs/web/setup/host-yourself/webfonts for more info.

*Note: you can structure your folder the way you deem fit.*


Both the `all.min.css` file and the `webfonts` folder are gotten from the zip file downloaded from the Font Awesome website.



It is imperative that you set up your directory tree as above with the empty files created in their respective folders before you proceed. 

### 2. The HTML Code

The HTML code features:

+ A form with an input field for text and a button to submit the entered city or country name to the OpenWeatherMap `API`

![-](https://blog.openreplay.com/images/build-a-weather-app-with-vanilla-js/images/aAPAYJa.png)
*form with submit button*
  

+ An input field for your unique OpenWeatherMap API key which receives focus if it is empty when you click on the submit button.


![-](https://blog.openreplay.com/images/build-a-weather-app-with-vanilla-js/images/ekQxeqZ.png)
*input field for your `API` key*
  

+ Text alongside animated icon fonts that notify the user when the data is either in transit or has been received.

![-](https://blog.openreplay.com/images/build-a-weather-app-with-vanilla-js/images/KxJaQ6A.png)
*status of the data in transit*


+ Tells the current day of the week and time in real time. The timer shown in the image below can be paused or resumed.

![-](https://blog.openreplay.com/images/build-a-weather-app-with-vanilla-js/images/umvy7xY.png)
*current time and day*
  


+ The data sent by the `API` gets displayed as illustrated in the images below

<img style='height:auto; width: 100%;' src="https://i.imgur.com/P72i3in.png"/>

*if you submitted the form while offline*


![-](https://blog.openreplay.com/images/build-a-weather-app-with-vanilla-js/images/Pu3jK8t.png)
*default text before any data is sent or received*


<img style='height:auto; width: 100%;' src="https://i.imgur.com/qJUBhK2.png"/>
*displaying received data for Abu Dhabi*


+ After receiving the data, you can choose to download it as either `XML` or `JSON` as shown in the image below

![-](https://blog.openreplay.com/images/build-a-weather-app-with-vanilla-js/images/6KLitZN.png)


### 3. Placing The Files In Their Required Folders:
Make sure the `tailwind.min.css`, `css.css`, `all.min.css` (from the `fontawesome` zip file), `fredoka.woff2` files and the `webfonts` folder are present as designated in the directory tree above and the page will render as shown above when viewed in a browser.


The links to the files required for the final results shown in the images above are given below;

+ [index.html](https://gist.githubusercontent.com/ogbotemi-2000/8b0146bb28c1c521e4983f56378202e9/raw/31cf78a2465af0753412c8f91421c77f4d0d34b2/index.html),
+ [css.css](https://gist.githubusercontent.com/ogbotemi-2000/632b0c7f4b5d5e18c6b27608e0dedff1/raw/a70607bc79624c72b4ecee797f1a0110ab5856b5/css.css).


Place them in your folder as shown in the directory tree shown above and the `index.html` file will render when opened in the browser as expected.



<h2>Session Replay for Developers</h2> 
<p><em>
Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data
</em></p>
<img alt="OpenReplay" width="768" height="400" src="https://blog.openreplay.com/assets/overview_Z17Qdyg.png" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em>





## Adding Interactivity To The Page Via JavaScript

### Writing The Code That Goes Into The Script Block In The `index.html` File

> It is imperative that you paste the code below into the script block present at the bottom of the `index.html` file.



The `beginTimer` function above is called when the page loads as shown in the code below.

The global variables defined in the `page.js` file are initialized below.

```javascript
w.addEventListener('DOMContentLoaded', function () {
  animations.addRippleAnimations(),
    today = (time_of_day = (main = document.querySelector('article.inset-0')).getElementsByTagName('button')[0]).previousElementSibling,
    time_of_day.firstChild.data = '| |';
  /* begins the incremental update to the time of day */
  beginTimer(time_of_day.lastElementChild, today)

  apiKey = main.querySelector('.api-key'),
    notifyUser = main.querySelector('.notify-user').lastChild,
    sentStatus = main.querySelector('.status');
});
```

### Defining Variables

All the global variables used in the entire context of the program are defined below. The `checkObj` function makes accessing both defined and undefined properties of any object straightforward. In its implementation below, `'[absent]'` is hardcoded as a fallback back return value for non-existent `object` properties.

```javascript

const promisify = cb => new Promise(resolve => setTimeout(_ => resolve(cb()))),
  _json = new XMLHttpRequest(),
  _xml = new XMLHttpRequest(),
  blobs = {},
  w = window;
let main, time_of_day, today, apiKey, notifyUser, sentStatus, buttons, verifyAPI, days = {
    'Mon': 'Monday',
    'Tue': 'Tuesday',
    'Wed': 'Wednesday',
    'Thu': 'Thursday',
    'Fri': 'Friday',
    'Sat': 'Saturday',
    'Sun': 'Sunday'
  },
  _toString = Object.prototype.toString,
  checkObj = function(json) {
    return function(prop, sendBack) {
      let split = prop.split('.'),
        results = [],
        previous, notArray, result;
      for (let i = 0, val, j; i++ < split.length;) {
        j = split[i - 1]; //done because i is automatically 1 at the start of the loop: needed for the heuristics below
        if (notArray = split.length === 1) {
          val = json[j],
            results[i - 1] = result = val ? val : '[absent]';
          break;
        }
        i === 1 ? val = previous = json[j] : val = (previous && = previous[j]);
        j = split.slice(0, i).join('.');
        if (!notArray) results[i - 1] = result = val ? val : '[absent]';
      }
      return sendBack ? result : void 0;
    }
  },
  verifyAPI;
```

### Defining The Function That Takes Custom City Or Country Names

When the `button` in the `form` in the `HTML` code above is clicked, it calls the `sendRequestedData` function which is defined as shown below;

The `sendRequestedData` appropriately notifies the user when either the `API` responds with no info for the requested data or when the user is offline

```javascript
function sendRequestedData() {
  if(navigator.onLine) {
    let input  = this.previousElementSibling.firstElementChild;
    if(apiKey.textContent) {
      send(`https://api.openweathermap.org/data/2.5/weather?q=${input.value}&appid=${apiKey.textContent}`)
      /*this function sends two AJAX requests
        to the openweathermap API endpoint 
      */
    } else {
      apiKey.focus()
      /*
      apiKey represents the input field where you will input your API key
      */
    }
  } else {
    notifyUser.data = 'You are offline'
  }
}
```

### Writing Code That Actually Sends The Data
The `senRequestedData` function defined above calls the `send` function which then makes two simultaneous `GET` requests via `_xml` and `_json` as shown below:

The second argument of the  `_xml.open` method appends `&mode=xml` to the `API` URL. This makes the `API` send the weather data as `xml` to the `_xml.onload` callback as will be seen below.

The `updateStatus` function notifies the user whether the has been received or requested based on the value of its `argument`

```javascript
//this function is the main entry into this entire program
function send(query) {
  _xml.open("GET", query + "&mode=xml"),
    _json.open("GET", query),
    _xml.send(),
    _json.send();
  updateStatus(false);
}
/*notifies the user that the data is either
 * in transit or has been received
 * by changing "Gets Weather Data" to
 * "Getting Weather Data" and vice-versa
 */
function updateStatus(flag) {
  let text, prop;
  text = flag
    ? ((prop = "remove"), "Gets Weather Data")
    : ((prop = "add"), "Getting Weather Data...");

  (sentStatus.lastChild.data = text),
    sentStatus.firstElementChild.lastElementChild.classList[prop]("animate");
}
```

### Handling The Data Received From The API

Below is the definition for `_json.onload` that gets called when the `API` responds with the requested data. The functions used in the code block below are all defined and explained in the code blocks after.


The `verifyAPI` function expression below uses a `closure` to `close over` the `json` argument passed to `checkObj` which returns an anonymous function to `verifyAPI` that is then used to check the properties on `res` - the `API` response string parsed into an object - `json`, as will be seen in the body of the `screenAndProcessAPI` function.

```javascript
/* To detect whether the API returned an error,
 * the response string is checked for the presence
 * "cod:200" as is in the return statement
 * of the function below
 */
function isError(string) {
  string = string.replace(/\{|\}|"|'/g, "").split(",")[0];
  string = string.split(":");
  return string[0] === "cod" && string[1] !== "200";
}
_xml.onload = function(evt) {
  let res = evt.target.response;
  if (isError(res)) {
    notifyUser.parentNode.classList.remove('invisible'), notifyUser.data = JSON.parse(res).message, updateStatus(false);
    return;
  }
  updateStatus(true)
  blobs['XML'] = new Blob([res],{
    type: 'application/xml'
  })
}
_json.onload = function (evt) {
  let res = evt.target.response;
  if (isError(res)) {
    let next;
    /* notify the user of the particular error message returned by the API */
    (next = notifyUser.parentNode).classList.remove("invisible"),
      (notifyUser.data = JSON.parse(res).message);
    (next = next.nextElementSibling), next && next.remove(), updateStatus(true);
    return;
  }
```
Explaining further about handling the data received in the `JSON`
```javascript
/* makeAwait makes the synchronous JSON.parse asynchronous,
   * screenAndProcessApi is where the heavy lifting occurs
   */
  makeAwait(JSON.parse(res)).then((json) => {
    updateStatus(true);
    screenAndProcessAPI(json);
  }),
    /* stores a blob of each API response for JSON and XML
     * which can then be downloaded as a link as will be seen
     */
    blobs["JSON"] = new Blob([res], {
      type: "application/json",
    });
};
```
### Defining Utility Functions
The functions below are required as utilities that are independent where they are defined or called:

```javascript

/* @param entity can be of any data type
 * @param type: an optional string that you want to assert to be true to the type of entity. It is case-insensitive
 * it urns the data type of entity if type is not defined otherwise, it returns a boolean
 */
function Is(entity, type) {
  let a =
    entity == void 0
      ? _toString(entity).replace(/\[object |\]/g, "")
      : entity.constructor.name;
  return type ? type === a || a.toUpperCase() === type.toUpperCase() : a;
}

/* takes any statement or function and makes it
 * asynchronous.
 * returns a promise
 */
async function makeAwait(entity) {
  return (await Is(entity, "Function")) ? entity() : entity;
}
```
### Displaying The Parsed And Formatted Data On The Page


The `screenAndProcessAPI` function, defined below, is such that it accepts an object as its argument which must define the properties it expects. The fallback string - `[absent]` is substituted for the properties that are undefined on the said `object`.


This function generates a `template string` which is then made the `innerHTML` of a node to be `appended` to the `DOM`, the node and its children have been tested and stylized using classes with `media query`  breakpoints for responsiveness as well as with icon fonts prior to their being used in code as a `template string`.

Subsequent calls to the `screenAndProcessAPI` function, akin to subsequent requests for the `API` data, simply removes the currently appended node and append new ones for each new data.

```javascript
function screenAndProcessApi(json) {
  let avail = notifyUser.parentElement.nextElementSibling,
    main = document.createElement("main"),
    fragment = document.createDocumentFragment(),
    fallback = "img/fallback.avif";
    
  main.className = "p-2 h-full w-full flex space-x-px sm:space-x-1";
  avail && avail.remove();
  main.innerHTML = `
    <section class="sm:space-y-1 w-1/5 sm:w-auto mr-1">
      <p class="md:right-0 md:text-base text-xs w-2/3 sm:truncate sm:w-auto sm:relative md:absolute md:top-full absolute transform text-right sm:translate-x-0 -translate-x-full top-2/3 sm:top-0 text-white sm:bg-transparent sm:p-0 p-1 bg-gray-100" style="--tw-bg-opacity: 0.2;">
       ${verifyAPI("name", !0)}, ${verifyAPI("sys.country", !0)}
      </p>
      <img style="--tw-bg-opacity: 0.2;" class='bg-yellow-100 object-contain sm:h-2/3 h-2/3 bg-white rounded-full p-1' src="http://openweathermap.org/img/w/${verifyAPI(
        "weather.0.icon",
        !0
      )}.png"/>
    </section>
    <section class="w-4/5 mr-2 flex relative flex-col space-y-1 fa-xs h-full overflow-y-scroll overflow-hidden pt-2">
      <div class="flex">
        <i class="fa fa-clock fa-lg mr-2"></i>timezone
        <div class="ml-2.5 mr-1">${verifyAPI("timezone", !0)}</div>
      </div>
      
```
### To show more of the parsed and formatted weather data on the page
there are section codes provided that have the relative data for the humidity, temperature, time zone, wind speed, etc. 


```javascript
<div class="flex">
        <div class="mr-3"><i class="dew-drop relative"></i><i class="fa fa-circle-notch"></i></div>
        humidity
        <div class="mr-0.5 ml-7">${verifyAPI("main.humidity", !0)}</div>%
      </div>
<div class="flex">
        <div class="mr-2 flex no-wrap items-center"><i class="fa fa-ruler-vertical"></i><i class="fa fa-compress"></i></div>
        pressure
        <div class="ml-7 mr-0.5">${verifyAPI("main.pressure", !0)}</div>hPa
      </div>
      </div>
    </section>
    </div>
    <div class="absolute right-0 top-full mt-7 text-xs flex flex-col space-y-0.5 text-black bg-gray-50 p-1 rounded-md" style="--tw-bg-opacity: 0.8;">
      <div class='flex justify-between items-center'><i class="fa fa-moon mx-2"></i><p>estimated at</p><time class="mx-1">${date_time(
        verifyAPI("sys.sunrise", !0) * 1000,
        "time"
      )}</time> PM</div>
      <div class='flex justify-between items-center'><i class="fa fa-sun text-base mx-2"></i>was at<time class="mx-1">${date_time(
        verifyAPI("sys.sunset", !0) * 1000,
        "time"
      )}</time> AM</div>
    </div>`;

  fragment.appendChild(main).querySelector("img").onerror = function () {
    this.src = fallback;
  };
  notifyUser.parentNode.classList.add("invisible"),
    notifyUser.parentNode.after(main);
}
```
### Defining The Function That Formats Numbers To Dates

The `date_time` function used in the code block above is important for getting human-readable days, weeks, months, and years from
`milliseconds` or `Date` objects.

```javascript
function date_time(date, which, split) {
  typeof date === "string" && (date = +date);
  if (date) {
    !Is(date, "Date") && (date = new Date(date));
    let time = (date += "").match(/[0-9]{2}:[0-9]{2}:[0-9]{2}/g)[0],
      value;
    date = date.match(/[0-9]{2}:[0-9]{2}:[0-9]{2}|^([^]+)\s+[0-9]+:/)[1];
    split && (date = date.split(" "));
    switch ((which &&= which.toLowerCase())) {
      case "date":
        value = date;
        break;
      case "time":
        value = time;
        break;
      default:
        value = [date, time];
    }
    return value;
  }
}
```

### Defining The Function That Handles Downloading Of The API Data
Finally, the code to save the fetched data as a chosen file type is as follows:

```javascript
function handleDownloadingOfFiles() {
  let notify = this.parentNode.nextElementSibling,
    selected = this.parentNode.querySelector("input:checked");
  if (selected && Object.keys(blobs).length) {
    let picked = blobs[selected.id];
    picked
      ? save(picked)
      : save(new Blob([blobs["JSON"]], { type: "text/plain" }));
  } else {
    clearTimeout(this.timeout),
      notify.classList.remove("invisible"),
      (this.timeout = setTimeout(
        (_) => notify.classList.add("invisible"),
        1300
      ));
  }
}
```

### Actual Download Of The API Data As XML Or JSON

The actual download is made possible by the `save` function in the code block above.
This `save` function takes a `blob` of data and constructs an `URL.createObjectUrl(blob)` which is then set as the href of an `anchor` tag. There are also fallbacks in the function body for when this approach does not work as seen below:

```javascript
function save(blob, name) {
  name = name || "download";

  // Use native saveAs in IE10+
  if (typeof navigator !== "undefined") {
    if (/MSIE [1-9]\./.test(navigator.userAgent)) {
      alert("IE is unsupported before IE10");
      return;
    }
    if (navigator.msSaveOrOpenBlob) {
      // https://msdn.microsoft.com/en-us/library/hh772332(v=vs.85).aspx
      alert("will download using IE10+ msSaveOrOpenBlob");
      navigator.msSaveOrOpenBlob(blob, name);
      return;
    }
  }

  // Construct URL object from blob
  var win_url = window.URL || window.webkitURL || window;
  var url = win_url.createObjectURL(blob);

  // Use a.download in HTML5
  var a = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
  if ("download" in a) {
    a.href = url;
    a.download = name;
    a.dispatchEvent(new MouseEvent("click"));
    // Don't revoke immediately, as it may prevent DL in some browsers
    setTimeout(function () {
      win_url.revokeObjectURL(url);
    }, 500);
    return;
  }

  // Use object URL directly
  window.location.href = url;
  // Don't revoke immediately, as it may prevent DL in some browsers
  setTimeout(function () {
    win_url.revokeObjectURL(url);
  }, 500);
}
```

You can read more about this approach of downloading files in the browser [here](https://github.com/eligrey/FileSaver.js/)

### Defining The Function That Displays The Current Day Of The Week And Time 


The `beginTimer` function below makes it possible to efficiently update the current time of the day via `requestAnimationFrame` which lets us call a callback whenever the browser is ready to paint the page.

It also determines the current day of the week.

More info about `requestAnimationFrame` can be found here: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame.

The timely timer on the page is made possible by the code below.
It even offers the ability to be paused and resumed.

```javascript
function beginTimer(node, to_day) {
  // Source: http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
  // shim layer with setTimeout fallback
  window.requestAnimFrame = (function () {
    return (
      window.requestAnimationFrame ||
      window.webkitRequestAnimationFrame ||
      window.mozRequestAnimationFrame ||
      function (callback) {
        window.setTimeout(callback, 1000 / 60);
      }
    );
  })();
  window.cancelAnimFrame =
    window.cancelAnimationFrame ||
    window.webkitCancelAnimationFrame ||
    window.mozCancelAnimationFrame ||
    window.clearInterval;

  var lastTime = new Date().getTime();

  (beginTimer.resume = function timer() {
    beginTimer.__frame = requestAnimFrame(timer);
    let date = new Date();
    var currentTime = date.getTime();

    if (currentTime - lastTime >= 1000) {
      lastTime = currentTime;
      (node.textContent = date.toLocaleString().split(",")[1]),
        (to_day.textContent = days[date.toDateString().split(" ").shift()]);
    }
  })();
  beginTimer.stop = (_) => {
    cancelAnimFrame(beginTimer.__frame);
  };
}
```


## Summary of The Code Logic
The `send` function is the entry point of the program, it is called by the `sendRequestedData` function which was called by the `click` `event` of the button in the form 
when submitted to the `API` via the `_xml` and `_json` `variables`



The response is parsed by `JSON.parse` which is made `asynchronous` by using the `makeAwait` function which returns a `promise` that exposes the parsed `string` as an object.

The `screenAndProcessAPI` function is called when the `json` response is fetched and parsed, without errors, into an `object`.

It builds a `DOM` `node` that displays the required info accessed from the `json` response on the page. Each concerned property on the object is checked for availability by the `verifyAPI` function expression which uses a fallback `string` - `[absent]` if it is not available

 
 
To allow options for saving as either `XML` or `JSON`, the responses for the simultaneous requests made by  `_xml` and `_json` are stored in the `blobs` object. 

Each stored response is downloaded as a file when, chosen by the `radio button`, the `save` button is clicked - an action which calls the `handleDownloadingOfFiles` function that then calls the `save` function with an `argument` of the `blob` representing the selected `radio`, after which the chosen file type is downloaded.

The `save` function downloads files by using the `new Blob` `constructor` to make a `blob` of a file and `URL.createObjectURL` to make a URL representing the aforementioned `blob` in memory, which is then linked to via an `anchor tag`'s `href` attribute. 

The `anchor tag` must have an `HTML5` `attribute`: `download` for the browser to download the file the `anchor tag` links to instead of viewing it.
 
## Connecting the App with the OpenWeatherMap API
The connection to the `API` is simply made by making `GET` requests via `AJAX` to the endpoint below:

```
https://api.openweathermap.org/data/2.5/weather?q={city or country}&appid={API key}.
```
The value for `q` is supplied by the custom value of the `input` field present in the app.

Below is a typical response parsed into `JSON` for valid and available cities sent by the `API` for "Ondo" - the city requested:

```json
{
  "coord": {
    "lon": 4.8333,
    "lat": 7.1
  },
  "weather": [
    {
      "id": 803,
      "main": "Clouds",
      "description": "broken clouds",
      "icon": "04n"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 295.05,
    "feels_like": 295.75,
    "temp_min": 295.05,
    "temp_max": 295.05,
    "pressure": 1011,
    "humidity": 94,
    "sea_level": 1011,
    "grnd_level": 981
  },
  "visibility": 10000,
  "wind": {
    "speed": 0.96,
    "deg": 273,
    "gust": 1.24
  },
  "clouds": {
    "all": 51
  },
  "dt": 1674258362,
  "sys": {
    "country": "NG",
    "sunrise": 1674280709,
    "sunset": 1674323091
  },
  "timezone": 3600,
  "id": 2326171,
  "name": "Ondo",
  "cod": 200
}
```
And here is the response for non-existent cities:
```json
{
  "cod": "404",
  "message": "city not found"
}
```

which the `isError` function above uses to know whether an error occurred or not.

## Conclusion
You can head over to the OpenWeatherMap website to look up multiple `APIs` that they offer for free. 

The various `API` calls available on the OpenWeatherMap website have differences in the ranges of time of their usage depending on the plan you are subscribed to.

Therefore, it is advisable to know what you need from the `API` before you start.


[![newsletter](https://blog.openreplay.com/newsletter_Z1P0UIP.png)](https://newsletter.openreplay.com/)