Build a Real-Time Weather Dashboard

Fetch live weather data from a REST API and display it on a deployed site.

Introduction

30 Second Summary

Every app on your phone that shows live information (weather forecasts, stock prices, sports scores) works by pulling data from an external source and displaying it on screen. This pattern of fetching and presenting data is the foundation of modern web development.

In this project, you will build a weather dashboard that fetches real-time data from the OpenWeatherMap API and displays it in a clean, responsive interface deployed to the web.

What You'll Build

You will create a browser-based weather dashboard where users type a city name and instantly see the current temperature, conditions, humidity, and wind speed.

By the end of this project, you'll have:

  • A GitHub repository containing your complete weather dashboard code with a clean commit history.
  • A working web app that calls a REST API, parses JSON responses, and renders dynamic content on the page.
  • A live website hosted on GitHub Pages that anyone can visit using a public URL.
  • Secret Mission: Add a 5-day forecast section that shows upcoming weather below the current conditions.

Are there any prerequisites?

This project assumes a basic understanding of HTML tags, CSS styling, and JavaScript functions. You do not need any prior experience with APIs or deploying websites.

Everything runs in the browser using GitHub Codespaces, so there is nothing to install on your computer.

Set Up Your Development Environment

Before you can build a weather dashboard, you need a place to write code and a source of weather data.

In this step, you will create a cloud-based coding environment using GitHub Codespaces and sign up for a free weather API that provides live data for any city in the world.

In this step, get ready to:
  • Create a GitHub repository and open it in Codespaces.
  • Sign up for OpenWeatherMap and get your free API key.
  • Create your project files and verify the setup with Live Server.
Create your GitHub repository and open Codespaces

You need a repository to store your code and a Codespace to edit it in the browser. A Codespace gives you a full VS Code editor with a terminal, all running in the cloud.

  • Go to github.com/new to create a new repository.
  • Enter weather-dashboard in the Repository name field.
  • Check the box for Add a README file.
  • Click Create repository.
  • On your new repository page, click the green Code button.
  • Select the Codespaces tab.
  • Click Create codespace on main.

A new browser tab opens with a VS Code editor connected to your repository. Wait for it to finish loading.

What is GitHub Codespaces?

GitHub Codespaces is a cloud-based development environment. It gives you a full VS Code editor, an integrated terminal, and Git pre-installed. Everything runs in your browser so you do not need to install anything on your computer.

Codespace not loading?

Make sure you checked Add a README file when creating the repository. Codespaces needs at least one file on the main branch to start. If you forgot, go to your repository settings and add a file manually, then try creating the Codespace again.

Help me troubleshoot my Codespace not loading

Sign up for OpenWeatherMap and get your API key

Your dashboard needs real weather data. OpenWeatherMap provides a free API that returns live temperature, conditions, humidity, and wind speed for any city.

What is an API key?

An API key is a unique code that identifies your app when it talks to a service. OpenWeatherMap uses it to track usage and ensure you stay within the free tier limits (60 calls per minute, 1,000,000 calls per month).

New API keys can take up to 10 minutes to activate. If your key does not work immediately, wait a few minutes before trying again.

Not seeing your API key?

Make sure you verified your email first. The API keys page only shows your key after email verification is complete. If you still do not see one, try refreshing the page or signing out and signing back in.

Help me find my OpenWeatherMap API key

Create your project files and verify the setup

With your Codespace running, you can now create the three files your project needs. You will also confirm that your editor can serve a live preview in the browser.

  • Create the three project files by running this command in the Codespace terminal:
touch index.html style.css script.js
  • Confirm the files exist by running:
ls

You should see index.html, style.css, and script.js listed in the output.

Don't see the files?

Make sure you typed the command in the Terminal panel at the bottom of the Codespace editor. If the terminal is not visible, open it by pressing Ctrl+` (backtick key, next to the 1 key on most keyboards).

Help me find the terminal in my Codespace

  • In the Explorer sidebar on the left, click index.html to open it in the editor.
  • Paste the following HTML into index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Weather Dashboard</title>
</head>
<body>
    <h1>Weather Dashboard</h1>
</body>
</html>

What does this code do?

This is a minimal HTML page. The <!DOCTYPE html> declaration tells the browser this is an HTML5 document. The <h1> tag displays a large heading so you can confirm the page renders correctly.

  • Save index.html by pressing Ctrl+S.
  • Install the Live Server extension by clicking the Extensions icon in the left sidebar (it looks like four small squares).
  • Type Live Server in the search box at the top.
  • Find the Live Server extension by Ritwick Dey and click Install.
  • Switch back to index.html by clicking its tab at the top of the editor.
  • Right-click anywhere inside the index.html editor and select Open with Live Server.

A browser preview opens (either in a new tab or inside the editor). You should see the heading "Weather Dashboard" displayed on the page.

What if the preview opens in a separate tab?

In Codespaces, the preview may open as a forwarded port in a new browser tab instead of inside the editor. Both work fine. The important thing is that you see the "Weather Dashboard" heading rendered on the page.

Not seeing Weather Dashboard in the preview?

Make sure you saved index.html before opening Live Server. Check that you pasted the HTML correctly and that the file has the <h1>Weather Dashboard</h1> line inside the <body> tags.

If Codespaces asks you to open a port or shows a notification about port forwarding, click Open in Browser to see your page.

Help me get Live Server working in my Codespace

✔️ Awesome, I've got everything!

Great. Make sure index.html is saved and your preview shows the heading.

ⓧ I'd like to double check the full code

Here is what each file should contain at this point:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Weather Dashboard</title>
</head>
<body>
    <h1>Weather Dashboard</h1>
</body>
</html>

Both style.css and script.js should be empty files with no content.

Your development environment is ready and your API key is waiting. Next up, you will build the HTML structure and CSS layout for your weather dashboard.

Build the HTML Structure and CSS Layout

Your environment is ready and your files are in place. Now it's time to build the visual foundation of your weather dashboard.

Every web app starts with two layers: structure (HTML) and presentation (CSS). In this step, you'll create the page layout that holds weather data and style it into a clean, professional-looking card.

In this step, get ready to:
  • Build the HTML page structure with a search input, button, and weather display section.
  • Style the dashboard with CSS for a clean card layout that works on all screen sizes.
Build the HTML page structure

Your HTML file needs a search input for the city name, a button to trigger the search, and containers that will display weather data once the JavaScript fills them in.

  • Open index.html in your Codespace editor by clicking it in the Explorer sidebar.
  • Select all the existing code and replace it with the following:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <!-- Makes the page scale correctly on mobile devices -->
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Weather Dashboard</title>
    <!-- Link to our external stylesheet -->
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Weather Dashboard</h1>
        <!-- Search input and button -->
        <div class="search-box">
            <input type="text" id="city-input" placeholder="Enter city name...">
            <button id="search-btn">Search</button>
        </div>
        <!-- Error message container, hidden by default -->
        <div id="error-message" class="error-message hidden"></div>
        <!-- Weather display card, hidden until data loads -->
        <div id="weather-card" class="weather-card hidden">
            <h2 id="city-name"></h2>
            <img id="weather-icon" src="" alt="Weather icon">
            <p id="temperature" class="temperature"></p>
            <p id="description" class="description"></p>
            <div class="details">
                <div class="detail-item">
                    <span class="label">Humidity</span>
                    <span id="humidity"></span>
                </div>
                <div class="detail-item">
                    <span class="label">Wind Speed</span>
                    <span id="wind-speed"></span>
                </div>
            </div>
        </div>
    </div>
    <!-- Link to our JavaScript file -->
    <script src="script.js"></script>
</body>
</html>

What does this code do?

  • The viewport meta tag tells mobile browsers to scale the page to the device width instead of zooming out.
  • The search-box div contains the text input and button the user interacts with.
  • The weather-card div holds all the weather data elements (city, icon, temperature, description, humidity, wind speed). It has a hidden class so it stays invisible until JavaScript populates it.
  • The <script> tag goes at the bottom of the body so the HTML elements load before the JavaScript tries to access them.
  • Save index.html by pressing Ctrl+S.
  • Check the Live Server preview tab in your browser. You should see the heading "Weather Dashboard", a text input with placeholder text "Enter city name...", and a Search button.

Don't see the input and button?

  • Make sure you replaced ALL the old HTML, not just added below it. The file should start with <!DOCTYPE html> and nothing else above it.
  • Check that Live Server is still running. If the preview tab closed, right-click index.html in the Explorer and select Open with Live Server.

Help me debug my HTML structure

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on to styling.

ⓧ I'd like to double check the full code

Here is what your complete index.html should look like:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Weather Dashboard</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Weather Dashboard</h1>
        <div class="search-box">
            <input type="text" id="city-input" placeholder="Enter city name...">
            <button id="search-btn">Search</button>
        </div>
        <div id="error-message" class="error-message hidden"></div>
        <div id="weather-card" class="weather-card hidden">
            <h2 id="city-name"></h2>
            <img id="weather-icon" src="" alt="Weather icon">
            <p id="temperature" class="temperature"></p>
            <p id="description" class="description"></p>
            <div class="details">
                <div class="detail-item">
                    <span class="label">Humidity</span>
                    <span id="humidity"></span>
                </div>
                <div class="detail-item">
                    <span class="label">Wind Speed</span>
                    <span id="wind-speed"></span>
                </div>
            </div>
        </div>
    </div>
    <script src="script.js"></script>
</body>
</html>
Style the page layout and search bar

Right now the page looks plain because there is no CSS yet. You'll use flexbox to center a card on the page and style the search input and button.

  • Open style.css in the Explorer sidebar.
  • Add the following CSS to set up the page layout, container card, and search bar styles:
/* Remove default browser margins and padding */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

/* Full-page purple gradient with centered content */
body {
    font-family: Arial, sans-serif;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #667eea, #764ba2);
    padding: 20px;
}

/* White card that holds all dashboard content */
.container {
    background: white;
    border-radius: 16px;
    padding: 40px 30px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
    text-align: center;
    width: 100%;
    max-width: 420px;
}

h1 {
    color: #333;
    margin-bottom: 20px;
    font-size: 1.6rem;
}

/* Input and button sit side by side */
.search-box {
    display: flex;
    gap: 8px;
    margin-bottom: 20px;
}

#city-input {
    flex: 1;
    padding: 12px 16px;
    border: 2px solid #e0e0e0;
    border-radius: 8px;
    font-size: 1rem;
    outline: none;
    transition: border-color 0.3s;
}

/* Highlight the input border when focused */
#city-input:focus {
    border-color: #667eea;
}

#search-btn {
    padding: 12px 20px;
    background: #667eea;
    color: white;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    cursor: pointer;
    transition: background 0.3s;
}

#search-btn:hover {
    background: #5a6fd6;
}

/* Gray out the button while a request is in progress */
#search-btn:disabled {
    background: #b0b0b0;
    cursor: not-allowed;
}

What does this code do?

  • The * selector resets all browser default spacing so every element starts from zero.
  • The body uses display: flex with align-items: center and justify-content: center to place the card perfectly in the middle of the viewport.
  • The .container class creates a white rounded card with a soft shadow. max-width: 420px keeps it compact.
  • The .search-box uses display: flex to lay the input and button side by side, with gap: 8px for spacing.
  • Save style.css and check the preview.
  • You should see a white card centered on a purple gradient background. The heading, search input, and purple Search button are styled and aligned.

Still seeing unstyled text?

  • Confirm the <link rel="stylesheet" href="style.css"> line exists inside the <head> of your index.html. Without it, the browser cannot find your stylesheet.
  • Check that the filename is exactly style.css (not styles.css or Style.css). File names are case-sensitive.

Help me fix my CSS layout

Style the weather display and add responsive rules

The weather card and its contents are hidden right now, but they still need styles ready for when JavaScript shows them. You'll also add a media query so the card looks good on smaller screens.

  • In style.css, add the following CSS below the code you just wrote:
/* Weather card spacing */
.weather-card {
    margin-top: 20px;
}

/* Utility class to hide elements until JS reveals them */
.hidden {
    display: none;
}

#city-name {
    font-size: 1.5rem;
    color: #333;
    margin-bottom: 8px;
}

#weather-icon {
    width: 100px;
    height: 100px;
}

.temperature {
    font-size: 2.5rem;
    font-weight: bold;
    color: #333;
    margin-bottom: 4px;
}

.description {
    font-size: 1.1rem;
    color: #666;
    text-transform: capitalize;
    margin-bottom: 20px;
}

/* Humidity and wind speed in a row */
.details {
    display: flex;
    justify-content: space-around;
    border-top: 1px solid #e0e0e0;
    padding-top: 16px;
}

.detail-item {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.label {
    font-size: 0.85rem;
    color: #999;
}

/* Red text for error messages */
.error-message {
    color: #e74c3c;
    margin-bottom: 16px;
    font-size: 0.95rem;
}

/* Shrink padding and fonts on narrow screens */
@media (max-width: 480px) {
    .container {
        padding: 24px 16px;
    }

    h1 {
        font-size: 1.3rem;
    }

    .temperature {
        font-size: 2rem;
    }
}

What does this code do?

  • The .hidden class sets display: none so the weather card and error message stay invisible until JavaScript removes this class.
  • The .details section uses justify-content: space-around to evenly distribute humidity and wind speed side by side.
  • The @media rule at the bottom reduces padding and font sizes when the screen is narrower than 480px, keeping the card readable on mobile.
  • Save style.css.
  • Resize the preview pane narrower (below 480px wide). You should see the card's padding shrink and the heading get slightly smaller.

Card looks the same at all sizes?

  • Make sure the @media block is at the very bottom of your CSS file, after all other rules.
  • Check for a missing closing brace } in the code above the media query. A single missing brace can break all styles below it.

Help me debug my responsive CSS

✔️ Awesome, I've got everything!

Great. Double check you've saved style.css before moving on.

ⓧ I'd like to double check the full code

Here is what your complete style.css should look like:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    min-height: 100vh;
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, #667eea, #764ba2);
    padding: 20px;
}

.container {
    background: white;
    border-radius: 16px;
    padding: 40px 30px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
    text-align: center;
    width: 100%;
    max-width: 420px;
}

h1 {
    color: #333;
    margin-bottom: 20px;
    font-size: 1.6rem;
}

.search-box {
    display: flex;
    gap: 8px;
    margin-bottom: 20px;
}

#city-input {
    flex: 1;
    padding: 12px 16px;
    border: 2px solid #e0e0e0;
    border-radius: 8px;
    font-size: 1rem;
    outline: none;
    transition: border-color 0.3s;
}

#city-input:focus {
    border-color: #667eea;
}

#search-btn {
    padding: 12px 20px;
    background: #667eea;
    color: white;
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    cursor: pointer;
    transition: background 0.3s;
}

#search-btn:hover {
    background: #5a6fd6;
}

#search-btn:disabled {
    background: #b0b0b0;
    cursor: not-allowed;
}

.weather-card {
    margin-top: 20px;
}

.hidden {
    display: none;
}

#city-name {
    font-size: 1.5rem;
    color: #333;
    margin-bottom: 8px;
}

#weather-icon {
    width: 100px;
    height: 100px;
}

.temperature {
    font-size: 2.5rem;
    font-weight: bold;
    color: #333;
    margin-bottom: 4px;
}

.description {
    font-size: 1.1rem;
    color: #666;
    text-transform: capitalize;
    margin-bottom: 20px;
}

.details {
    display: flex;
    justify-content: space-around;
    border-top: 1px solid #e0e0e0;
    padding-top: 16px;
}

.detail-item {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.label {
    font-size: 0.85rem;
    color: #999;
}

.error-message {
    color: #e74c3c;
    margin-bottom: 16px;
    font-size: 0.95rem;
}

@media (max-width: 480px) {
    .container {
        padding: 24px 16px;
    }

    h1 {
        font-size: 1.3rem;
    }

    .temperature {
        font-size: 2rem;
    }
}

Your dashboard now has a polished visual structure. Next up, you'll bring it to life by fetching real weather data from the OpenWeatherMap API.

Fetch Live Weather Data from the API

Your dashboard looks great, but right now it is all static HTML and CSS. The real power of a web app comes from pulling live data from external services.

In this step, you will connect your dashboard to the OpenWeatherMap API using JavaScript's built-in fetch() function. By the end, typing a city name and pressing Enter will pull real weather data into your browser console.

In this step, get ready to:
  • Write a function that calls the OpenWeatherMap API with a city name.
  • Test the API call and confirm real weather data appears in your browser console.
  • Connect the search button and Enter key to trigger the API call.
Write the fetch function

Your JavaScript needs a few things before it can talk to the API: the API key, references to the HTML elements it will update, and the function that actually makes the network request.

  • Open script.js in your Codespace editor.
  • Add your API key and DOM element references at the top of the file by pasting this code:
const API_KEY = "your-api-key-here";
const cityInput = document.getElementById("city-input");
const searchBtn = document.getElementById("search-btn");
const weatherCard = document.getElementById("weather-card");
const errorMessage = document.getElementById("error-message");

What does this code do?

API_KEY stores your OpenWeatherMap key so you can reference it later without repeating it.

The four document.getElementById() calls grab references to the HTML elements your script will show, hide, or update. Caching them in variables avoids searching the DOM repeatedly.

  • Replace your-api-key-here with the actual API key you copied from OpenWeatherMap in Step 1.

Keep your key safe

For a personal learning project this approach is fine. In production apps, you would store secrets in environment variables or a backend service so they are never exposed in client-side code.

Now add the function that actually calls the API. It constructs the request URL, sends it with fetch(), and handles errors gracefully.

  • Below your DOM references, add the fetchWeather function by pasting this code:
async function fetchWeather(city) {
    // Build the API URL with the city name, metric units, and your key
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&units=metric&appid=${API_KEY}`;

    try {
        const response = await fetch(url);

        if (!response.ok) {
            if (response.status === 404) {
                showError("City not found. Please check the spelling and try again.");
            } else {
                showError("Something went wrong. Please try again later.");
            }
            return;
        }

        const data = await response.json();
        console.log(data);
    } catch (error) {
        showError("Unable to connect. Please check your internet connection.");
    } finally {
        searchBtn.disabled = false;
    }
}

What does this code do?

encodeURIComponent(city) safely encodes the city name so spaces and special characters do not break the URL.

units=metric tells OpenWeatherMap to return temperatures in Celsius instead of the default Kelvin.

The try/catch/finally structure handles three scenarios: a successful response (parse and log), an HTTP error like 404 (show a message), or a network failure (show a connection error). The finally block always re-enables the search button so the user is never stuck.

The fetchWeather function calls showError() when something goes wrong, but that function does not exist yet.

  • Add the showError helper function below fetchWeather by pasting this code:
function showError(message) {
    // Display the error message and hide the weather card
    errorMessage.textContent = message;
    errorMessage.classList.remove("hidden");
    weatherCard.classList.add("hidden");
}

What does this code do?

showError sets the text inside the #error-message element, removes the hidden class to make it visible, and hides the weather card so the user only sees the error.

  • Save script.js.

You will not see any visible change in the preview yet because nothing is calling these functions. The next substep will fix that.

Seeing a red underline in your editor?

If you see a squiggly line under showError inside fetchWeather, make sure you added the showError function below it. JavaScript hoists function declarations, so the order in the file does not matter at runtime. The editor warning may disappear once you save.

help me debug my script.js file

Test the API call in the console

Before wiring up the button, let's verify the API connection works by calling fetchWeather directly with a hardcoded city name.

  • At the very bottom of script.js, add this temporary test line:
fetchWeather("London");
  • Save script.js.
  • In the Codespace, click the **Ports** tab in the bottom panel.
  • Right-click the port where your Live Server is running and select **Open in Browser**.
  • In the new browser tab that opens, press **F12** (or **Cmd+Option+I** on macOS) to open DevTools.
  • Click the **Console** tab in DevTools.

You should see a large JSON object logged in the console containing weather data for London, including fields like main.temp, weather[0].description, and wind.speed.

What is this JSON object?

This is the raw response from the OpenWeatherMap Current Weather API. It contains the city name, temperature, humidity, wind speed, a weather description, and an icon code. In the next step, you will take these values and display them on the page.

Console showing an error instead of data?

If you see a 401 error, your API key may still be activating. New OpenWeatherMap keys can take up to 10 minutes to become active. Wait a few minutes and refresh the page.

If you see a 404 error, double-check that the URL in your code matches the endpoint exactly: https://api.openweathermap.org/data/2.5/weather.

help me debug my OpenWeatherMap API call

  • Once you see the weather data in the console, go back to script.js and delete the fetchWeather("London"); line at the bottom.
  • Save the file.
Connect the search button and Enter key

The API call works, but users should not have to edit the code to search for a city. You need event listeners that trigger fetchWeather when the user clicks the button or presses Enter.

  • In script.js, add the following code between your DOM references and the fetchWeather function:
searchBtn.addEventListener("click", handleSearch);
cityInput.addEventListener("keydown", function (event) {
    if (event.key === "Enter") {
        handleSearch();
    }
});

function handleSearch() {
    const city = cityInput.value.trim();
    // Do nothing if the input is empty
    if (!city) return;

    // Disable button and hide previous results while loading
    searchBtn.disabled = true;
    errorMessage.classList.add("hidden");
    weatherCard.classList.add("hidden");

    fetchWeather(city);
}

What does this code do?

The first listener calls handleSearch() when the search button is clicked. The second listener calls it when the user presses the Enter key inside the input field.

handleSearch() reads and trims the input value, exits early if it is empty, disables the button to prevent spam clicks, hides any previous error or weather card, and then calls fetchWeather(city) with the user's input.

  • Save script.js.
  • Switch to your browser tab with the app open and refresh the page.
  • Type London in the search field and press **Enter** (or click **Search**).
  • Open the DevTools console (**F12**, then the **Console** tab).

You should see the weather data object for London logged in the console, just like before. The button disables briefly while the request is in flight, then re-enables.

Nothing happens when you click Search?

Make sure your event listeners are placed after the DOM references (searchBtn must be defined before you call addEventListener on it).

Check the console for any syntax errors. A missing closing brace or parenthesis will prevent the entire file from running.

help me fix my event listeners

✔️ Awesome, I've got everything!

Great work. Double check you have saved script.js and confirmed you can see weather data in the console.

ⓧ I'd like to double check the full code

Here is the complete script.js at the end of this step:

const API_KEY = "your-api-key-here";
const cityInput = document.getElementById("city-input");
const searchBtn = document.getElementById("search-btn");
const weatherCard = document.getElementById("weather-card");
const errorMessage = document.getElementById("error-message");

searchBtn.addEventListener("click", handleSearch);
cityInput.addEventListener("keydown", function (event) {
    if (event.key === "Enter") {
        handleSearch();
    }
});

function handleSearch() {
    const city = cityInput.value.trim();
    if (!city) return;

    searchBtn.disabled = true;
    errorMessage.classList.add("hidden");
    weatherCard.classList.add("hidden");

    fetchWeather(city);
}

async function fetchWeather(city) {
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&units=metric&appid=${API_KEY}`;

    try {
        const response = await fetch(url);

        if (!response.ok) {
            if (response.status === 404) {
                showError("City not found. Please check the spelling and try again.");
            } else {
                showError("Something went wrong. Please try again later.");
            }
            return;
        }

        const data = await response.json();
        console.log(data);
    } catch (error) {
        showError("Unable to connect. Please check your internet connection.");
    } finally {
        searchBtn.disabled = false;
    }
}

function showError(message) {
    errorMessage.textContent = message;
    errorMessage.classList.remove("hidden");
    weatherCard.classList.add("hidden");
}

Your app is now pulling real weather data from the internet. Next up, you will take that console data and display it beautifully on the page.

Display Weather Data on the Page

Your fetch function is working. You can see raw weather data flowing into the console every time you search for a city.

Now it is time to take that raw data and turn it into something visual. In this step, you will write a function that grabs specific pieces from the API response and places them into the HTML elements you built earlier.

In this step, get ready to:
  • Write a function that renders weather data into the page.
  • Test error handling for invalid city names.
Write the displayWeather function

The API response is a nested JSON object containing the city name, temperature, weather conditions, humidity, and wind speed. Your displayWeather function will extract each piece and place it into the correct HTML element.

  • In script.js, scroll to the bottom of the file (below the showError function).
  • Add the displayWeather function by pasting the following code:
function displayWeather(data) {
    // Set the city name heading
    document.getElementById("city-name").textContent = data.name;
    // Round the temperature and add the Celsius symbol
    document.getElementById("temperature").textContent = `${Math.round(data.main.temp)}°C`;
    // Set the weather description text
    document.getElementById("description").textContent = data.weather[0].description;
    // Display humidity as a percentage
    document.getElementById("humidity").textContent = `${data.main.humidity}%`;
    // Display wind speed in meters per second
    document.getElementById("wind-speed").textContent = `${data.wind.speed} m/s`;
    // Build the weather icon URL using the icon code from the response
    document.getElementById("weather-icon").src = `https://openweathermap.org/img/wn/${data.weather[0].icon}@2x.png`;
    document.getElementById("weather-icon").alt = data.weather[0].description;

    // Reveal the weather card and hide any previous errors
    weatherCard.classList.remove("hidden");
    errorMessage.classList.add("hidden");
}

What does this code do?

Each line targets one HTML element by its id and fills it with a specific piece of the API response.

  • data.name holds the city name that the API matched.
  • Math.round(data.main.temp) rounds the temperature to a whole number. The units=metric parameter in your URL means this arrives in Celsius.
  • data.weather[0].icon is a short code like 10d. Plugging it into the icon URL gives you a matching weather image from OpenWeatherMap.
  • Removing the hidden class from weatherCard makes the card appear. It was hidden until there was data to show.

Now you need to connect this function to your existing fetch logic. Currently, fetchWeather logs data to the console instead of displaying it.

  • In script.js, scroll up to the fetchWeather function and find this line inside the try block:
        const data = await response.json();
        console.log(data);
  • Replace console.log(data) with displayWeather(data), so the two lines now look like this:
        const data = await response.json();
        displayWeather(data);
  • Save script.js.
  • In the browser preview, type Tokyo into the search field and press Enter.

You should see the city name, a temperature in Celsius, a weather icon, a description, humidity percentage, and wind speed all rendered inside the white card.

Not seeing weather data on the page?

  • Make sure you saved script.js after editing. Unsaved changes will not take effect.
  • Check that you replaced console.log(data) with displayWeather(data) and did not accidentally delete the line above it (const data = await response.json()).
  • Open DevTools (F12) and check the Console tab for any red error messages. A typo in a function or element name will show up there.

Still stuck? Help me debug why my displayWeather function is not showing data on the page.

Test the error handling

Your fetchWeather function already handles errors gracefully. When a user types a city that does not exist, the API returns a 404 status code. Your code catches this and displays a friendly message instead of crashing.

  • In the search field, type a nonsense word like xyzabc and press Enter.

You should see a red error message that says "City not found. Please check the spelling and try again." The weather card stays hidden.

How does the error handling work?

  • if (!response.ok) checks whether the HTTP status code indicates a problem (anything outside the 200-299 range).
  • A 404 means the city was not found. Any other error code gets a generic fallback message.
  • The catch block handles network failures (like losing your internet connection).
  • The finally block re-enables the search button no matter what happened. This prevents the button from staying permanently greyed out.
  • Now type Tokyo again and press Enter.

The error message disappears and valid weather data takes its place. The error and success states never show at the same time.

Error message not appearing for invalid cities?

  • Check that the showError function references errorMessage (the variable defined at the top of your file).
  • Make sure the return statement is still present after the showError calls inside the if (!response.ok) block. Without it, the code falls through and tries to parse a non-JSON error response.

Need more help? Help me figure out why my error handling is not showing the City not found message.

✔️ Awesome, I've got everything!

Great. Double check you have saved script.js.

ⓧ I'd like to double check the full code

Here is the full script.js file at this point. Compare it against yours line by line.

const API_KEY = "your-api-key-here";
const cityInput = document.getElementById("city-input");
const searchBtn = document.getElementById("search-btn");
const weatherCard = document.getElementById("weather-card");
const errorMessage = document.getElementById("error-message");

searchBtn.addEventListener("click", handleSearch);
cityInput.addEventListener("keydown", function (event) {
    if (event.key === "Enter") {
        handleSearch();
    }
});

function handleSearch() {
    const city = cityInput.value.trim();
    if (!city) return;

    searchBtn.disabled = true;
    errorMessage.classList.add("hidden");
    weatherCard.classList.add("hidden");

    fetchWeather(city);
}

async function fetchWeather(city) {
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${encodeURIComponent(city)}&units=metric&appid=${API_KEY}`;

    try {
        const response = await fetch(url);

        if (!response.ok) {
            if (response.status === 404) {
                showError("City not found. Please check the spelling and try again.");
            } else {
                showError("Something went wrong. Please try again later.");
            }
            return;
        }

        const data = await response.json();
        displayWeather(data);
    } catch (error) {
        showError("Unable to connect. Please check your internet connection.");
    } finally {
        searchBtn.disabled = false;
    }
}

function displayWeather(data) {
    document.getElementById("city-name").textContent = data.name;
    document.getElementById("temperature").textContent = `${Math.round(data.main.temp)}°C`;
    document.getElementById("description").textContent = data.weather[0].description;
    document.getElementById("humidity").textContent = `${data.main.humidity}%`;
    document.getElementById("wind-speed").textContent = `${data.wind.speed} m/s`;
    document.getElementById("weather-icon").src = `https://openweathermap.org/img/wn/${data.weather[0].icon}@2x.png`;
    document.getElementById("weather-icon").alt = data.weather[0].description;

    weatherCard.classList.remove("hidden");
    errorMessage.classList.add("hidden");
}

function showError(message) {
    errorMessage.textContent = message;
    errorMessage.classList.remove("hidden");
    weatherCard.classList.add("hidden");
}

Your dashboard is fully functional. Users can search for any city and see real weather data rendered cleanly on the page. Next up, you will deploy this to the internet so anyone can use it.

Deploy to GitHub Pages

Your weather dashboard works perfectly in Codespaces. Now it is time to make it available to anyone on the internet.

GitHub Pages is a free hosting service that serves static websites directly from a GitHub repository. You will push your code to your repository and enable Pages to get a live URL you can share with anyone.

In this step, get ready to:
  • Commit and push your files to GitHub.
  • Enable GitHub Pages on your repository.
  • Visit your live weather dashboard on the internet.
Commit and push your code to GitHub

Your files only exist inside your Codespace right now. Committing and pushing saves them permanently to your GitHub repository, where GitHub Pages can serve them.

  • Stage all your files for commit by running this command in your Codespace terminal:
git add .
  • Create a commit with a descriptive message by running:
git commit -m "Add weather dashboard"
  • Push your commit to the remote repository on GitHub by running:
git push

What do these commands do?

  • git add . stages every file in your project folder, telling Git you want to include them in the next snapshot.
  • git commit -m "Add weather dashboard" creates a snapshot (commit) of your staged files with a message describing what changed.
  • git push uploads your local commit to the remote repository on GitHub.com.
  • Confirm the push succeeded by checking the terminal output. You should see a message mentioning main -> main and a URL to your repository.

Seeing an error when you push?

  • If you see a message about authentication, try closing and reopening your Codespace. Codespaces handles Git authentication automatically, but occasionally needs a fresh session.
  • If you see "nothing to commit", make sure you saved all three files (index.html, style.css, script.js) before running git add ..

Help me troubleshoot my git push error in GitHub Codespaces.

Enable GitHub Pages on your repository

Your code is now on GitHub, but the repository does not serve it as a website yet. You need to turn on GitHub Pages in your repository settings.

  • Open your weather-dashboard repository on GitHub.com. You can click the repository name in the top-left corner of your Codespace, or navigate directly to github.com and find it from your profile.
  • Click the Settings tab in the top navigation bar of your repository.
  • In the left sidebar, under Code and automation, click Pages.
  • Under Build and deployment, find the Source dropdown and select Deploy from a branch.
  • In the branch dropdown that appears, select main.
  • Leave the folder dropdown set to / (root).
  • Click Save.

What is GitHub Pages?

GitHub Pages takes the files in your repository and serves them as a website. Because your project is plain HTML, CSS, and JavaScript with no build step, Pages can serve the files directly from the main branch.

Deployment takes 1-2 minutes. GitHub runs a workflow behind the scenes to publish your files.

Visit your live site

After a minute or two, your site will be live at a public URL based on your GitHub username.

  • Open a new browser tab and visit your site at:
https://[[USERNAME="your-github-username"]].github.io/weather-dashboard/
  • Type a city name like Tokyo in the search box and press Enter. You should see live weather data appear on your publicly hosted site.
  • Try an invalid city name to confirm your error handling still works on the live version.

Seeing a 404 page?

  • GitHub Pages can take 1-2 minutes to deploy. Wait a moment and refresh the page.
  • Check that your URL uses your exact GitHub username (case-sensitive) and ends with /weather-dashboard/ including the trailing slash.
  • Go back to Settings > Pages and confirm the source is set to main branch with / (root) folder.

Help me figure out why my GitHub Pages site is showing a 404 error.

Share your link

Your GitHub Pages URL is permanent and free. Send it to a friend or post it on social media. Anyone with the link can use your weather dashboard.

Secret mission

Add a 5-Day Forecast

Your dashboard shows the current weather, but what about tomorrow? In this secret mission, you'll call a second API endpoint, loop through an array of forecast data, and dynamically generate a row of forecast cards below the current conditions.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on free tiers, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • GitHub repository (weather-dashboard) with GitHub Pages site.
  • GitHub Codespaces instance (free tier: 60 hours/month on a 2-core machine, 15 GB storage).
  • OpenWeatherMap API key (free tier: 60 calls/minute, 1,000,000 calls/month).

Keep everything running

No action needed. Choose this if you are still actively building or want your live site to stay up.

  • Your GitHub Pages site stays live indefinitely at no cost.
  • Your Codespace will auto-stop after 30 minutes of inactivity and auto-delete after 30 days of inactivity.
  • Your OpenWeatherMap API key remains active on the free tier with no charges.

Pause - I'll come back to this later

Stop your Codespace to free up compute usage while preserving your work.

  • Go to https://github.com/codespaces.
  • Click the ellipsis (...) to the right of your Codespace.
  • Click Stop codespace.
  • Your saved changes remain intact. Reopen the Codespace any time from the same page.

Delete - I don't want to use this again

Remove your Codespace and optionally your repository to start completely fresh.

  • Delete your Codespace: Go to https://github.com/codespaces, click the ellipsis (...) next to your Codespace, then click Delete.
  • Delete your repository: Go to your weather-dashboard repository on GitHub. Click Settings. Scroll down to the Danger Zone section and click Delete this repository. Follow the prompts to confirm. This also removes your GitHub Pages site.
  • Revoke your API key: Go to https://home.openweathermap.org/api_keys and delete the key you created for this project.

That's a Wrap!

That's a Wrap!

Nice work! You just built a fully functional weather dashboard that pulls live data from a real API and deployed it as a live website anyone can visit.

You've learned how to:

  • Built a responsive weather dashboard using HTML, CSS, and JavaScript that handles user input, loading states, and error cases gracefully.
  • Connected to the OpenWeatherMap REST API using fetch(), parsed JSON responses, and rendered dynamic content to the DOM.
  • Deployed a live website to GitHub Pages using a professional Git workflow with commits and pushes.
  • Secret Mission: Extended the app with a 5-day forecast by calling a second API endpoint, filtering forecast data, and dynamically generating HTML elements.

Ready to quiz yourself?