Project Tutorial · JavaScript

Build a Weather App Using Only Free Public APIs

📅 March 2025 ⏱ 15 min read 🏷️ JavaScript · Project · Weather API

In this step-by-step tutorial, you'll build a fully functional weather dashboard using Open-Meteo — a completely free weather API with no registration, no API key, and no rate-limit anxiety. By the end, you'll have a weather app that shows current conditions, temperature, wind speed, and a 7-day forecast for any city in the world. Full source code included.

Project Overview & What You'll Build

We're building a weather app that: accepts a city name typed by the user, converts that city name to GPS coordinates, fetches real-time weather from Open-Meteo, and displays temperature, weather condition, wind speed, humidity, and a 7-day forecast. All for free, with no API key.

Prerequisites:

The Free APIs We'll Use

Open-Meteo (open-meteo.com) — A completely free weather API that provides current conditions and forecasts for any coordinates. No API key, no registration, 10,000 free requests per day.

Open-Meteo Geocoding API (geocoding-api.open-meteo.com) — Also from Open-Meteo, this converts city names to latitude/longitude coordinates. Free, no key needed.

🌍 Why Open-Meteo?

Unlike most weather APIs, Open-Meteo is genuinely free forever for non-commercial use — no credit card, no free tier that expires, no sneaky limits. It uses open-source weather models (GFS, ECMWF) and is accurate enough for real production apps. It's the best free weather API available in 2025.

Step 1: HTML Structure

STEP 1

Create an index.html file with this basic structure. We'll fill in the JavaScript over the next steps:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Free Weather App</title>
  <style>
    body { font-family: sans-serif; max-width: 500px; margin: 2rem auto; padding: 1rem; }
    input { width: 100%; padding: 0.75rem; font-size: 1rem; margin-bottom: 0.5rem; }
    button { padding: 0.75rem 1.5rem; background: #00e5a0; border: none; cursor: pointer; font-size: 1rem; }
    #weather { margin-top: 2rem; padding: 1.5rem; border: 1px solid #ddd; border-radius: 8px; display: none; }
    #error { color: red; margin-top: 1rem; }
    .forecast { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 1rem; }
    .day { border: 1px solid #ddd; padding: 0.5rem; border-radius: 6px; text-align: center; font-size: 0.85rem; }
  </style>
</head>
<body>
  <h1>🌤️ Free Weather App</h1>
  <input id="cityInput" type="text" placeholder="Enter a city name..." />
  <button onclick="getWeather()">Get Weather</button>
  <div id="error"></div>
  <div id="weather">
    <h2 id="cityName"></h2>
    <p id="temp"></p>
    <p id="wind"></p>
    <p id="condition"></p>
    <div class="forecast" id="forecast"></div>
  </div>
  <script>/* JavaScript goes here */</script>
</body>
</html>

Step 2: Get Coordinates from a City Name

STEP 2

Open-Meteo needs latitude/longitude, not a city name. So the first API call converts the city name to coordinates using the free geocoding API:

// Convert a city name to lat/lng using Open-Meteo's free geocoding API
async function getCityCoords(cityName) {
  const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1`;
  const response = await fetch(url);
  const data = await response.json();

  if (!data.results || data.results.length === 0) {
    throw new Error('City not found. Try a different spelling.');
  }

  const { name, latitude, longitude, country } = data.results[0];
  return { name, latitude, longitude, country };
}

The encodeURIComponent() call is important — it safely encodes special characters in city names (like "São Paulo" or "Zürich") into URL-safe format. Always encode user input before putting it in a URL.

Step 3: Fetch Weather Data

STEP 3

Now use the coordinates to call the actual weather API. We'll request current weather plus a 7-day daily forecast:

// Fetch weather data from the free Open-Meteo API
async function fetchWeather(lat, lng) {
  const params = new URLSearchParams({
    latitude: lat,
    longitude: lng,
    current_weather: 'true',
    daily: 'temperature_2m_max,temperature_2m_min,weathercode',
    timezone: 'auto'   // Auto-detect timezone based on coordinates
  });

  const url = `https://api.open-meteo.com/v1/forecast?${params}`;
  const response = await fetch(url);

  if (!response.ok) throw new Error('Weather API request failed');
  return response.json();
}

Using URLSearchParams is the clean way to build query strings — far better than string concatenation, and it handles encoding automatically.

Step 4: Display the Weather

STEP 4

Open-Meteo returns a weathercode (WMO weather code) for conditions instead of a text description. Here's a function to convert those codes to human-readable labels:

// WMO Weather codes → human-readable condition + emoji
function getCondition(code) {
  const conditions = {
    0: '☀️ Clear sky', 1: '🌤️ Mainly clear', 2: '⛅ Partly cloudy',
    3: '☁️ Overcast', 45: '🌫️ Foggy', 61: '🌧️ Light rain',
    63: '🌧️ Moderate rain', 65: '🌧️ Heavy rain',
    71: '🌨️ Light snow', 73: '❄️ Moderate snow',
    80: '🌦️ Rain showers', 95: '⛈️ Thunderstorm'
  };
  return conditions[code] || '🌡️ Unknown';
}

// Update the DOM with weather data
function displayWeather(city, country, weatherData) {
  const { current_weather } = weatherData;
  document.getElementById('cityName').textContent = `${city}, ${country}`;
  document.getElementById('temp').textContent = `🌡️ ${current_weather.temperature}°C`;
  document.getElementById('wind').textContent = `💨 Wind: ${current_weather.windspeed} km/h`;
  document.getElementById('condition').textContent = getCondition(current_weather.weathercode);
  document.getElementById('weather').style.display = 'block';
}

Step 5: Add the 7-Day Forecast

STEP 5
// Render the 7-day forecast cards
function displayForecast(daily) {
  const days = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
  const html = daily.time.map((dateStr, i) => {
    const dayName = days[new Date(dateStr).getDay()];
    const max = Math.round(daily.temperature_2m_max[i]);
    const min = Math.round(daily.temperature_2m_min[i]);
    const icon = getCondition(daily.weathercode[i]).split(' ')[0]; // emoji only
    return `<div class="day"><b>${dayName}</b><br>${icon}<br>${max}° / ${min}°</div>`;
  }).join('');
  document.getElementById('forecast').innerHTML = html;
}

Complete Source Code

Here's the complete <script> block that ties everything together:

async function getWeather() {
  const city = document.getElementById('cityInput').value.trim();
  const errorEl = document.getElementById('error');
  errorEl.textContent = '';

  if (!city) { errorEl.textContent = 'Please enter a city name.'; return; }

  try {
    // API Call 1: Convert city name → coordinates (free geocoding API)
    const { name, latitude, longitude, country } = await getCityCoords(city);

    // API Call 2: Fetch weather for those coordinates (free weather API)
    const weatherData = await fetchWeather(latitude, longitude);

    // Update the UI
    displayWeather(name, country, weatherData);
    displayForecast(weatherData.daily);

  } catch (err) {
    errorEl.textContent = err.message;
    document.getElementById('weather').style.display = 'none';
  }
}

// Allow pressing Enter to search
document.getElementById('cityInput')
  .addEventListener('keydown', e => e.key === 'Enter' && getWeather());

Next Steps & Enhancements

1. Add a loading state. Show a spinner while the API calls are in progress. Set it visible before the try block and hidden in both the success path and catch block.

2. Add Celsius/Fahrenheit toggle. Open-Meteo supports &temperature_unit=fahrenheit as a query parameter — no conversion math needed.

3. Use the browser's Geolocation API. Instead of typing a city, call navigator.geolocation.getCurrentPosition() to get the user's coordinates automatically, then skip the geocoding step entirely.

4. Add hourly forecasts. Open-Meteo's hourly parameter supports dozens of variables including UV index, precipitation probability, and cloud cover.

5. Style it properly. The CSS above is minimal by design. Add your own color scheme, animations, and layout to turn this into a real portfolio piece.

🚀 Find More Free APIs for Projects

Browse all 1,500+ free public APIs in our directory — including weather, crypto, space, sports, and more. View the Complete API Directory →