Build a weather app using JavaScript: step - by - step tutorial šŸš€

Build a weather app using JavaScript: step - by - step tutorial šŸš€

Ā·

9 min read

If you are familiar with programming, you will agree that one of the important concepts you must learn is API.

API (Application Programming Interface) makes it easy for us to communicate with third-party libraries, files, and get access to data.

In this tutorial, I will show you how to build a beautiful weather app using HTML, CSS, and JavaScript. Weā€™ll use the Open Weather API to fetch the current weather information.

Our app will display the user's city, country, temperature, and humidity.

Demo app

Image description

The tutorial covers the following:

  • Text validation
  • CSS styling
  • JSON to string
  • Geolocation
  • API & moreā€¦

Letā€™s dive into it.

`Step 0 ā€“ Setting up the project

Open the IDE or code editor of your choice. Create a new folder, I will name mine weather app. For the sake of this tutorial, I will be using Visual Studio Code. Moreso, I provided the image link for this tutorial.

We need to create our blank files index.html, style.css, and script.js. Create a folder in your project to store the images we will use. `

Image description

It's time to write the basic HTML code to setup your project. In the code below, I changed the title name to "Weather App".

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Weather App</title>
</head>
<body>


</body>
</html>

Add the CSS link and Box Icon

After the </title> closing tag, we can write the code to connect our css file, and Box Icon link. The Box Icon CDN will be used to generate icons in our app. Next, we can link the JavaScript file below the </div> tag.

index.html

</title>
    <!-- css file -->
    <link rel="stylesheet" href="style.css">
    <!-- Box Icon plugin -->
    <link href="https://unpkg.com/boxicons@2.0.9/css/boxicons.min.css" rel="stylesheet">
</head>
<body>
    <div class="wrapper">
 <!-- Your code here -->
    </div>
    <!-- Js file -->
    <script src="script.js"></script>

Create The HTML Layout for the app

Inside the <body> tag of our index.html file, we need to create a <div> with a class name of wrapper

<div class="wrapper">

        <!-- your code here -->

    </div>

In the <header> tag, let us create a left arrow icon (<-). The icon is generated from Box Icon. Then, give your app a preferred name. Mine is ā€œWeather Appā€

<div class="wrapper">
        <!-- your code here -->
     <header><i class="bx bx-left-arrow-alt"></i>Weather App</header>
    </div>

We need to create two <section> tags inside our <div>. The first <section> will contain our input and button

<div class="wrapper">
        <div class="wrapper">
            <header><i class="bx bx-left-arrow-alt"></i>Weather App</header>
            <!-- input and button -->
            <section class="input-part">
                <p class="info-text"></p>
                <input type="text" placeholder="Enter city here..." spellcheck="false" required>
                <div class="separator"></div>
                <button>Get Device Location</button>
            </section>
    </div>

At the end of the </section> tag, we can write the code for the second <section> to display the weather information.

</section>
<!-- display weather info -->
        <section class="weather-part">
            <img src="icons/cloud.svg" alt="weather icon">
            <div class="temp">
                <span class="numb">_</span> <span class="deg">Ā°</span>C
            </div>

            <div class="weather">_ _</div>
            <div class="location">
                <i class="bx bx-map"></i>
                <span>_, _</span>
            </div>
            <div class="bottom-details">
                <div class="column feels">
                    <i class="bx bxs-thermometer"></i>
                    <div class="details">
                        <div class="temp">
                            <span class="numb-2">_</span>
                            <span class="deg">Ā°</span>C
                        </div>
                        <P>Feels like</P>
                    </div>
                </div>

                <div class="column humidity">
                    <i class="bx bxs-droplet-half"></i>
                    <div class="details">
                            <span>_</span>
                            <p>Humidity</p>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    </div>

This is everything you need to do inside the <div>

The result of our code will look as so:

Image description

Note: the Box Icon will not be generated if your internet connection is not connected.

Step 1 ā€“ Styling our HTML code in style.css

Our HTML code looks ugly, we need to make the best out of it using CSS. Open your style.css file and letā€™s get it done. We will start by adding a few changes to the HTML code.The asterisk () sign in CSS is useful when you want to set a style for all the elements of an HTML page or for all of the elements within an element of an HTML page. The () class will contain the following attributes: margin, padding, box-sizing, font-family

style.css

*{
    margin: 0; 
    padding: 0; 
    box-sizing: border-box;
    font-family: 'Segoe UI', Tahoma, Verdana, sans-serif;
}

Styling the <body>, <div>, classes and elements

The code below will be used to style the body of our HTML code.

body{
    display: flex;
    min-height: 100vh;
    align-items: center;
    justify-content: center;
    background-color: rgb(86, 11, 165);
}

Now, it's time to style our wrapper, elements and classes in our first <section> for the input. Next, we have to style the second

which houses the weather details of our app.

.wrapper{
    width: 400px;
    border-radius: 7px;
    background-color: #fff;
}

.wrapper header{
    color: rgb(86, 11, 165);
    font-size: 21px;
    font-weight: 500;
    padding: 16px 16px;
    display: flex;
    align-items: center;
    border-bottom: 1px solid #ededed;
}

header i{
    cursor: pointer;
    font-size: 0px;
    margin-right: 8px;
    color: #000;
}

.wrapper.active header i{
    font-size: 30px;
    margin-left: 5px;
}

.wrapper .input-part{
    margin: 20px 25px 30px;
}

.wrapper.active .input-part{
    display:none;
}

.input-part :where(input, button){
    width: 100%;
    height: 55px;
    border: none;
    outline: none;
    font-size: 18px;
    border-radius: 7px;
}

/* input required style */
.input-part input::is(:focus, :valid){
    border: 2px solid #43AFFC;
}

.input-part input{
    text-align: center;
    border: 1px solid #bfbfbf;
}

.input-part .info-text{
    display: none;
    margin-bottom: 15px;
    padding: 12px 10px;
    text-align: center;
    border-radius: 7px;
    font-size: 17px;
}

.info-text.error{
    display: block;
    color: #721c24;
    background-color: #f8d7da;
    border: 1px solid #f5c6cb;
}

.info-text.pending{
    display: block;
    color: #0c5460;
    background: #d1ecf1;
    border: 1px solid #f5c6cb;
}

.input-part .separator{
    height: 1px;
    width: 100%;
    margin: 20px 0;
    background: #ccc;
    display: flex;
    align-items: center;
    justify-content: center;
}

.separator::before{
    content: "or";
    color: #ccc;
    font-size: 19px;
    padding: 0 15px;
    background: #fff;

}

.input-part button{
    color: #fff;
    cursor: pointer;
    background: rgb(86, 11, 165);
}

/* weather style */
.wrapper .weather-part{
    margin: 30px 0 0;
    display: none;
    align-items: center;
    justify-content: center;
    flex-direction: column;
}

.wrapper.active .weather-part{
    display: flex;
}

.weather-part img{
    max-width: 125px;
}

.weather-part .temp{
    display: flex;
    font-size: 72px;
    font-weight: 500;
}

.weather-part .temp .numb{
    font-weight: 600;
}

.weather-part .temp .deg{
    font-size: 40px;
    margin: 10px 5px 0 0;
    display: block;
}

.weather-part .weather{
    font-size: 21px;
    text-align: center;
    margin: -5px 20px 15px;
}

.weather-part .location{
    display: flex;
    align-items: center;
    font-size: 21px;
    margin-bottom: 30px;
}

.location i{
    font-size: 22px;
    margin-right: 5px;
}

.weather-part .bottom-details{
    width: 100%;
    display: flex;
    align-items: center;
    border-top: 1px solid #bfbfbf;
    justify-content: space-between;
}

.bottom-details .column{
    width: 100%;
    display: flex;
    padding: 15px 0;
    align-items: center;
    justify-content: center;
}

.column i{
    color: #43AFFC;
    font-size: 40px;
}

.column.humidity{
    border-left: 1px solid #bfbfbf;
}

.details .temp, .humidity span{
    font-size: 18px;
    font-weight: 500;
    margin-top: -3px;
}

.details .temp .deg{
    margin: 0;
    font-size: 17px;
    padding: 0 2px 0 1px;
}

.details p{
    font-size: 14px;
    margin-top: -6px;
}

Once you complete the CSS code your result should look like the image below šŸ˜ƒ

Image description

We are almost there. Letā€™s add some functionality to our app using JavaScript.

Step 2 - Add Functionality to the app

Finally, we are at the last part. Open the script.js file. The first thing we need to do inside the script.js file is to find the HTML elements based on their tag name and class.

script.js

const wrapper = document.querySelector(".wrapper")
inputPart = document.querySelector(".input-part")
infoText = document.querySelector(".info-text")
inputField = document.querySelector("input")
getLocationBtn = document.querySelector("button")
arrowBack = wrapper.querySelector("header i")
weatherIcon = document.querySelector("weather-part img")

API Key Before we proceed further, you will need an API key to get the weather information.

Follow the steps below To get the API key:

  • sign up or login to OpenWeather if you have an existing account
  • Click on your username at the top right corner and select My API Keys from the dropdown

Image description

Your API key will be displayed as seen below. Copy and paste it into your notepad or any text editor on your computer.

Image description

Open Weather API šŸ‘

We are still on the website, time to get the weather API for our project. The process for that is explained below. Copy and paste the API into your text editor.

Image description

Wrapping up šŸ˜Ž

Since we have copied the necessary files from OpenWeather, we can go back to the script.js file to make use of the code.

We need to create two variables, one for the API and the other for the API key.

let api
var apiKey = "paste the api key hereā€¦"

Next, let's create functions to handle our API requests. Firstly, I will create a function which will request the API from the server. For the API to search the weather information by cities, it must be specified in the code as seen below.

function requestApi()

function requestApi(city){
    //..addded {&units=metric} to the api to round up the number to the nearest 
    api = `https://api.openweathermap.org/data/2.5/weather?q=${city}&units=metric&appid=${apiKey}`
    fectchWeatherData()
}

fectchWeatherData() This will display an information to notify the user about the process of the app in a <p>tag. Thereafter, we can get the data from the server and return the API response by passing it into our app as a JavaScript object.

.then() calls the weatherDetails() where we pass in our result.

fectchWeatherData()

function fectchWeatherData(){
    infoText.innerText="Getting weather info..."
    infoText.classList.add("pending")
//...get server and return api response
    fetch(api).then(response => response.json()).
    then(result => weatherDetails(result)) 
}

weatherDetails()

function weatherDetails(info){
    infoText.classList.replace("pending", "error") //..our css style changes the text info background 
    if(info.cod == "404"){ //..cod is an object called from the weather api
        infoText.innerText = `You entered ${inputField.value} which isn't a valid city` //..checks for validation
    } else{
         //..get api data to properties in info-text   
        const city = info.name
        const country = info.sys.country
        const {description, id} = info.weather[0]
        const{feels_like,humidity, temp} = info.main

        //..parse the above values into the html elements 
        wrapper.querySelector(".temp, .numb").innerText = Math.floor(temp) //..round up number to nearest Integer
        wrapper.querySelector(".weather").innerHTML = description
        wrapper.querySelector(".location span").innerHTML = `${city}, ${country}`
        wrapper.querySelector(".temp .numb-2").innerHTML = Math.floor(feels_like)
        wrapper.querySelector(".humidity span").innerHTML = `${humidity}%`
       infoText.classList.remove("pending", "error") //..if we get the correct city from the api we hide pending and error message
        wrapper.classList.add("active") //..show the dashboard which displays the weather info
    }
}

Get userā€™s current location

We can make our app detect the userā€™s current location whenever the button is clicked, and the permission is accepted by the user.

getLocationBtn.addEventListener("click", ()=>{
    if(navigator.geolocation){ //..if user's browser supports geolocation
        navigator.geolocation.getCurrentPosition(onSuccess, onError)
    } else {
        alert("Browser doesn't support geolocation api")
    }
})

If the user grants the request, we'll get the weather details of the user's location. Else an error message will be displayed if the user declines.

To perform this we can write the code in a function called onSuccess() and onError()

function onSuccess(position){
    const {latitude, longitude} = position.coords //..getting the lat and long from coordinator object
    api = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&units=metric&appid=${apiKey}`
    //..addded {&units=metric} to the api to round up the number
    fectchWeatherData()
}

function onError(error){
  infoText.innerText = error.message //..html text will display error message
  infoText.classList.add("error")
}

Image description

The Input Field

When the user type an input and press the enter button on the keyboard the app will fetch the weather information from the server.

inputField.addEventListener("keyup", e=>{
    //... input value is not empty 
    if(e.key == "Enter" && inputField.value !=""){
        requestApi(inputField.value)
    }
})

The app fulfills its purpose if you run the code, But did you notice the back arrow isnā€™t working? šŸ¤”

Image description

Letā€™s fix that by writing this line of code

arrowBack.addEventListener("click", ()=>{
    wrapper.classList.remove("active")
})

Output

Image description

Pat yourself at the back for making it thus far šŸ˜ šŸ„°

Feel free to share to help someone ā˜ŗļø

Get the code šŸš€

Ā