Back to roadmaps vanilla-js Course

Project 2: Weather Dashboard

In this project, we will build a Weather Dashboard that communicates with a public API. We will fetch weather statistics for any city input, parse the JSON response, handle network/HTTP errors, and update the webpage structure dynamically.

1. Project Features

  • City name search input.
  • Fetch temperature, weather descriptions, and icons.
  • Informative error banners for missing or misspelled cities.
  • Async loader states during API requests.

2. The HTML Structure

<!-- index.html -->
<div class="weather-card">
  <h2>Weather Tracker</h2>

  <div class="search-box">
    <input type="text" id="city-input" placeholder="Enter city name..." value="London">
    <button id="search-btn">Search</button>
  </div>

  <!-- Loading state container -->
  <div id="loader" class="hidden">Loading...</div>

  <!-- Result Container -->
  <div id="weather-info" class="hidden">
    <h3 id="city-name">London, GB</h3>
    <img id="weather-icon" src="" alt="Weather Icon">
    <h1 id="temp">15°C</h1>
    <p id="desc">Cloudy</p>
  </div>

  <!-- Error Box -->
  <div id="error-box" class="hidden">City not found. Please try again.</div>
</div>

3. The JavaScript Implementation

For this project, we can query the free OpenWeatherMap API (or a mock service to test).

// app.js

const API_KEY = "YOUR_API_KEY_HERE"; // Substitute with your real OpenWeatherMap key
const BASE_URL = "https://api.openweathermap.org/data/2.5/weather";

const cityInput = document.querySelector('#city-input');
const searchBtn = document.querySelector('#search-btn');
const loader = document.querySelector('#loader');
const infoBox = document.querySelector('#weather-info');
const errorBox = document.querySelector('#error-box');

// Target Nodes
const cityNameNode = document.querySelector('#city-name');
const iconNode = document.querySelector('#weather-icon');
const tempNode = document.querySelector('#temp');
const descNode = document.querySelector('#desc');

// 1. Core Fetch Logic
async function fetchWeather(city) {
  // Show loader, hide result and errors
  loader.classList.remove('hidden');
  infoBox.classList.add('hidden');
  errorBox.classList.add('hidden');

  try {
    const url = `${BASE_URL}?q=${encodeURIComponent(city)}&units=metric&appid=${API_KEY}`;
    const response = await fetch(url);

    // Check if query is valid (e.g. 200 OK)
    if (!response.ok) {
      throw new Error("City not found");
    }

    const data = await response.json();
    displayWeather(data);
  } catch (error) {
    showError(error.message);
  } finally {
    loader.classList.add('hidden');
  }
}

// 2. Rendering Data to DOM
function displayWeather(data) {
  cityNameNode.textContent = `${data.name}, ${data.sys.country}`;
  tempNode.textContent = `${Math.round(data.main.temp)}°C`;
  descNode.textContent = data.weather[0].description;

  // Load official OpenWeatherMap icon
  const iconCode = data.weather[0].icon;
  iconNode.src = `https://openweathermap.org/img/wn/${iconCode}@2x.png`;

  infoBox.classList.remove('hidden');
}

// 3. Handling Errors
function showError(message) {
  errorBox.textContent = message;
  errorBox.classList.remove('hidden');
}

// 4. Bind Search Click event
searchBtn.addEventListener('click', () => {
  const city = cityInput.value.trim();
  if (city) {
    fetchWeather(city);
  }
});

// 5. Trigger query when hitting "Enter" inside input box
cityInput.addEventListener('keypress', (e) => {
  if (e.key === 'Enter') {
    searchBtn.click();
  }
});

4. Key Takeaways

  1. encodeURIComponent(): Always encode query parameters to prevent syntax errors caused by space characters in names (e.g., "New York").
  2. Robust Loader UX: Informing users that an action is running is essential for web accessibility and user trust.
  3. Graceful Degradation: Always handle errors inside catch to prevent scripts from breaking when APIs go down.

In the next project, we will build a fully functioning Calculator focusing on Event Delegation.

Published on Last updated: