FFreeAPI Hub
Back to Blog
API Development

Build a Weather App with Free APIs: A Complete Step-by-Step Tutorial

Learn to build a fully functional weather application using the free Open-Meteo API. No API key required. Includes geocoding, forecasts, and error handling in Python and JavaScript.

Jun 3, 202615 minFreeAPI Hub Team

Building a weather app is the classic API tutorial for a reason. It touches every fundamental skill: making HTTP requests, parsing JSON, handling errors, and presenting data to users. In this tutorial, we will build a real weather application using the Open-Meteo API, which is completely free, requires no API key, and offers both current conditions and forecasts.

Why Open-Meteo?

Open-Meteo is an open-source weather API that provides global weather data from national weather services. It stands out among free weather APIs for several reasons. It requires no registration or API key, which means you can start coding immediately. It offers a generous free tier of 10,000 calls per day for non-commercial use. It provides hourly forecasts up to 7 days and daily forecasts up to 16 days. And it supports both geocoding and reverse geocoding, so you can search by city name.

What You Will Build

By the end of this tutorial, you will have a weather app that takes a city name, fetches current conditions and a 7-day forecast, and displays the results with proper error handling.

Step 1: Geocoding, Converting City Names to Coordinates

Weather APIs work with latitude and longitude, but users search by city name. Open-Meteo provides a geocoding endpoint that converts city names to coordinates. This is our first API call.

python
import requests

def geocode_city(city_name):
    """Convert a city name to latitude and longitude."""
    url = "https://geocoding-api.open-meteo.com/v1/search"
    params = {"name": city_name, "count": 1, "language": "en"}
    
    response = requests.get(url, params=params)
    response.raise_for_status()
    data = response.json()
    
    if not data.get("results"):
        raise ValueError(f"City '{city_name}' not found")
    
    result = data["results"][0]
    return {
        "name": result["name"],
        "country": result.get("country", ""),
        "latitude": result["latitude"],
        "longitude": result["longitude"],
    }

# Test it
location = geocode_city("Tokyo")
print(f"{location['name']}, {location['country']}")
print(f"Lat: {location['latitude']}, Lon: {location['longitude']}")

The geocoding endpoint returns a list of matching locations. We take the first result and extract the coordinates. If no matches are found, we raise a clear error message. Always validate the response structure before accessing nested fields, as API responses can vary.

Step 2: Fetching Weather Data

Now that we have coordinates, we can fetch weather data. The Open-Meteo forecast endpoint accepts a wide range of parameters to customize what data you receive. For our app, we want current temperature, wind speed, and a 7-day forecast.

python
def get_weather(latitude, longitude):
    """Fetch current weather and 7-day forecast."""
    url = "https://api.open-meteo.com/v1/forecast"
    params = {
        "latitude": latitude,
        "longitude": longitude,
        "current": "temperature_2m,wind_speed_10m,weather_code,relative_humidity_2m",
        "daily": "weather_code,temperature_2m_max,temperature_2m_min",
        "timezone": "auto",
        "forecast_days": 7,
    }
    
    response = requests.get(url, params=params)
    response.raise_for_status()
    return response.json()

# Fetch weather for Tokyo
weather = get_weather(location["latitude"], location["longitude"])
current = weather["current"]
print(f"Temperature: {current['temperature_2m']}°C")
print(f"Wind: {current['wind_speed_10m']} km/h")
print(f"Humidity: {current['relative_humidity_2m']}%")

The current parameter specifies which weather variables to include in the current conditions. The daily parameter does the same for the daily forecast. The weather_code is a WMO (World Meteorological Organization) code that represents the overall weather condition, like sunny, cloudy, or rain.

Step 3: Decoding Weather Codes

Weather codes are numeric, but users need readable descriptions. The WMO weather interpretation codes map to human-readable strings. Here is a complete mapping you can use in your application.

javascript
// WMO Weather interpretation codes
const WEATHER_CODES = {
  0: "Clear sky",
  1: "Mainly clear",
  2: "Partly cloudy",
  3: "Overcast",
  45: "Foggy",
  48: "Depositing rime fog",
  51: "Light drizzle",
  53: "Moderate drizzle",
  55: "Dense drizzle",
  56: "Light freezing drizzle",
  57: "Dense freezing drizzle",
  61: "Slight rain",
  63: "Moderate rain",
  65: "Heavy rain",
  66: "Light freezing rain",
  67: "Heavy freezing rain",
  71: "Slight snow fall",
  73: "Moderate snow fall",
  75: "Heavy snow fall",
  77: "Snow grains",
  80: "Slight rain showers",
  81: "Moderate rain showers",
  82: "Violent rain showers",
  85: "Slight snow showers",
  86: "Heavy snow showers",
  95: "Thunderstorm",
  96: "Thunderstorm with slight hail",
  99: "Thunderstorm with heavy hail",
};

function describeWeather(code) {
  return WEATHER_CODES[code] || "Unknown";
}

Step 4: Putting It All Together

Now let us build a complete weather application that ties everything together. This version includes error handling, formatted output, and a clean user interface in the terminal. The same structure applies whether you are building a web app, mobile app, or CLI tool.

python
import requests

WEATHER_CODES = {
    0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
    45: "Foggy", 48: "Rime fog", 51: "Light drizzle", 53: "Moderate drizzle",
    55: "Dense drizzle", 61: "Slight rain", 63: "Moderate rain", 65: "Heavy rain",
    71: "Slight snow", 73: "Moderate snow", 75: "Heavy snow", 80: "Rain showers",
    81: "Moderate showers", 82: "Violent showers", 95: "Thunderstorm",
}

class WeatherApp:
    def __init__(self):
        self.session = requests.Session()
    
    def search(self, city_name):
        try:
            location = self._geocode(city_name)
            weather = self._get_weather(location)
            self._display(location, weather)
        except requests.ConnectionError:
            print("Error: Could not connect to the weather service.")
        except ValueError as e:
            print(f"Error: {e}")
        except Exception as e:
            print(f"Unexpected error: {e}")
    
    def _geocode(self, city_name):
        url = "https://geocoding-api.open-meteo.com/v1/search"
        res = self.session.get(url, params={"name": city_name, "count": 1})
        res.raise_for_status()
        data = res.json()
        if not data.get("results"):
            raise ValueError(f"City '{city_name}' not found")
        r = data["results"][0]
        return {"name": r["name"], "country": r.get("country", ""),
                "lat": r["latitude"], "lon": r["longitude"]}
    
    def _get_weather(self, loc):
        url = "https://api.open-meteo.com/v1/forecast"
        params = {
            "latitude": loc["lat"], "longitude": loc["lon"],
            "current": "temperature_2m,wind_speed_10m,relative_humidity_2m,weather_code",
            "daily": "weather_code,temperature_2m_max,temperature_2m_min",
            "timezone": "auto", "forecast_days": 7,
        }
        res = self.session.get(url, params=params)
        res.raise_for_status()
        return res.json()
    
    def _display(self, location, weather):
        current = weather["current"]
        daily = weather["daily"]
        
        print(f"\n{'=' * 40}")
        print(f"  Weather for {location['name']}, {location['country']}")
        print(f"{'=' * 40}")
        
        code = current["weather_code"]
        print(f"  Condition: {WEATHER_CODES.get(code, 'Unknown')}")
        print(f"  Temperature: {current['temperature_2m']}°C")
        print(f"  Humidity: {current['relative_humidity_2m']}%")
        print(f"  Wind: {current['wind_speed_10m']} km/h")
        
        print(f"\n  7-Day Forecast:")
        print(f"  {'-' * 36}")
        for i, date in enumerate(daily["time"]):
            cond = WEATHER_CODES.get(daily["weather_code"][i], "Unknown")
            hi = daily["temperature_2m_max"][i]
            lo = daily["temperature_2m_min"][i]
            print(f"  {date}  {cond:20s}  {lo}°C - {hi}°C")
        print()

# Run the app
app = WeatherApp()
app.search("San Francisco")

Step 5: Building a Web Version

For a web application, the logic is the same but the presentation layer changes. Here is a JavaScript version using fetch that you can drop into any frontend framework. The key difference is that all API calls are asynchronous, so you need to handle loading states and errors in the UI.

javascript
async function getWeather(cityName) {
  // Step 1: Geocode the city name
  const geoRes = await fetch(
    `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1`
  );
  const geoData = await geoRes.json();
  
  if (!geoData.results || geoData.results.length === 0) {
    throw new Error(`City "${cityName}" not found`);
  }
  
  const location = geoData.results[0];
  
  // Step 2: Fetch weather data
  const params = new URLSearchParams({
    latitude: location.latitude,
    longitude: location.longitude,
    current: "temperature_2m,wind_speed_10m,relative_humidity_2m,weather_code",
    daily: "weather_code,temperature_2m_max,temperature_2m_min",
    timezone: "auto",
    forecast_days: "7",
  });
  
  const weatherRes = await fetch(
    `https://api.open-meteo.com/v1/forecast?${params}`
  );
  const weather = await weatherRes.json();
  
  return { location, weather };
}

// UI integration
async function handleSearch(city) {
  try {
    document.getElementById("loading").style.display = "block";
    const { location, weather } = await getWeather(city);
    renderWeather(location, weather);
  } catch (error) {
    document.getElementById("error").textContent = error.message;
  } finally {
    document.getElementById("loading").style.display = "none";
  }
}

Next Steps and Improvements

You now have a working weather app. Here are some ways to extend it. Add geolocation support using the browser Geolocation API to detect the user location automatically. Cache responses to avoid redundant API calls. Add unit conversion between Celsius and Fahrenheit. Display weather icons instead of text descriptions. Build an hourly forecast view using the hourly data endpoint.

The Open-Meteo API also offers specialized endpoints for marine weather, air quality, and climate data. Explore the full API documentation to discover what else you can build. And check out the Weather category on FreeAPI Hub for alternative weather APIs with different features and coverage areas.

weather apiopen-meteopython tutorialjavascript