What is the OpenWeatherMap API?
The OpenWeatherMap API is a powerful, RESTful web service that provides access to current weather data, forecasts, historical weather data, and a variety of meteorological information for any location on Earth. It serves as the data backbone for thousands of weather applications, websites, and IoT devices worldwide. The API returns structured JSON responses containing temperature, humidity, wind speed, atmospheric pressure, weather conditions, sunrise/sunset times, and much more — all derived from a global network of weather stations, radar systems, and satellite data.
Why Use OpenWeatherMap for Your Weather App?
OpenWeatherMap stands out as the go-to choice for weather data integration for several compelling reasons:
- Generous free tier — The free plan offers 1,000 API calls per day (or 60 calls per minute with a monthly limit), which is perfect for learning, prototyping, and small-scale applications.
- Global coverage — Access weather data for over 200,000 cities and any geographic coordinate worldwide, making it suitable for both local and international applications.
- Comprehensive data — Beyond basic temperature readings, the API delivers humidity, pressure, wind speed/direction, visibility, UV index, air pollution indices, and multi-language weather descriptions.
- Multiple data formats — Choose between Current Weather, Hourly Forecast (4-day), Daily Forecast (16-day), OneCall API (combined current + forecast), and historical weather archives.
- Well-documented — The extensive documentation, code examples, and active developer community make integration straightforward even for beginners.
- Weather icons — Built-in weather condition codes and icon URLs allow you to visually represent weather states without creating your own icon set.
Getting Started — API Key and Setup
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Before writing a single line of code, you need to obtain an API key. Here's the process:
- Visit openweathermap.org and click "Sign Up" to create a free account.
- After verifying your email, log in and navigate to the "API Keys" section in your account dashboard.
- You'll find a default API key already generated — copy this key. It will look something like:
a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6. - Important: The free API key may take up to 2 hours to activate after initial sign-up. Plan accordingly.
- Store your API key securely. Never commit it to public GitHub repositories. Use environment variables or configuration files excluded from version control.
Understanding the API Endpoints
OpenWeatherMap offers several endpoints. For a standard weather app, the most relevant are:
Current Weather Data Endpoint
https://api.openweathermap.org/data/2.5/weather?q={city}&appid={API_KEY}&units=metric
This endpoint returns current weather conditions for a specified city. Parameters include the city name, your API key, and optional units (metric for Celsius, imperial for Fahrenheit, or leave blank for Kelvin). You can also search by city ID, geographic coordinates, or ZIP code.
5-Day Forecast Endpoint (3-Hour Intervals)
https://api.openweathermap.org/data/2.5/forecast?q={city}&appid={API_KEY}&units=metric
This returns weather forecasts in 3-hour intervals for the next 5 days (40 data points total). Each forecast block contains temperature, weather conditions, wind, and atmospheric data at a specific timestamp.
Response Structure for Current Weather
A typical JSON response from the current weather endpoint looks like this:
{
"coord": { "lon": -0.1257, "lat": 51.5085 },
"weather": [
{
"id": 802,
"main": "Clouds",
"description": "scattered clouds",
"icon": "03d"
}
],
"main": {
"temp": 15.2,
"feels_like": 14.8,
"temp_min": 13.9,
"temp_max": 16.5,
"pressure": 1012,
"humidity": 72
},
"wind": {
"speed": 4.1,
"deg": 320
},
"clouds": { "all": 40 },
"dt": 1684924800,
"sys": {
"country": "GB",
"sunrise": 1684904400,
"sunset": 1684959600
},
"timezone": 3600,
"id": 2643743,
"name": "London",
"cod": 200
}
Key fields to extract: main.temp for temperature, weather[0].description for the human-readable condition, weather[0].icon for the weather icon code, main.humidity for humidity percentage, and wind.speed for wind speed.
Building the Weather App — Step by Step
Project Structure
We'll build a single-page weather application using vanilla HTML, CSS, and JavaScript — no frameworks, no build tools. This approach ensures the tutorial is accessible to developers at any level. The app will feature:
- A search input where users type a city name
- Current weather display with temperature, conditions, humidity, and wind
- A weather icon that visually represents conditions
- A 5-day forecast strip showing future weather trends
- Error handling for invalid city names or network failures
- Responsive design that works on mobile and desktop
HTML — Building the User Interface
We start with a clean, semantic HTML structure. The layout consists of a header with the app title, a search form, a main weather display card, and a forecast section with horizontally-scrollable forecast cards.
<!-- The HTML skeleton for our weather app -->
<div class="container">
<header>
<h1>WeatherVue</h1>
<p>Real-time weather at your fingertips</p>
</header>
<div class="search-section">
<form id="searchForm">
<input
type="text"
id="cityInput"
placeholder="Enter city name..."
autocomplete="off"
required
/>
<button type="submit" id="searchBtn">
<span class="search-icon">🔍</span> Search
</button>
</form>
<p class="error-message" id="errorMsg"></p>
</div>
<div class="weather-display" id="weatherDisplay">
<!-- Dynamically populated by JavaScript -->
<div class="loading-spinner" id="loadingSpinner">
<div class="spinner"></div>
<p>Fetching weather data...</p>
</div>
</div>
<div class="forecast-section" id="forecastSection">
<h2>5-Day Forecast</h2>
<div class="forecast-cards" id="forecastCards">
<!-- Dynamically populated by JavaScript -->
</div>
</div>
</div>
CSS — Styling the App
The CSS creates a modern, card-based design with gradient backgrounds, smooth transitions, and responsive breakpoints. We use CSS custom properties (variables) for consistent theming.
/* CSS Variables for consistent theming */
:root {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-card: #0f3460;
--accent: #e94560;
--text-primary: #ffffff;
--text-secondary: #a0a0b0;
--border-radius: 12px;
--shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
color: var(--text-primary);
min-height: 100vh;
display: flex;
justify-content: center;
padding: 20px;
}
.container {
max-width: 800px;
width: 100%;
}
header {
text-align: center;
margin-bottom: 30px;
}
header h1 {
font-size: 2.5rem;
background: linear-gradient(135deg, #e94560, #ff6b6b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
header p {
color: var(--text-secondary);
font-size: 1rem;
margin-top: 8px;
}
/* Search Section */
.search-section {
margin-bottom: 30px;
}
#searchForm {
display: flex;
gap: 10px;
}
#cityInput {
flex: 1;
padding: 14px 18px;
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: var(--border-radius);
background: rgba(255, 255, 255, 0.05);
color: var(--text-primary);
font-size: 1rem;
outline: none;
transition: border-color 0.3s, box-shadow 0.3s;
}
#cityInput:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(233, 69, 96, 0.2);
}
#searchBtn {
padding: 14px 24px;
background: var(--accent);
color: white;
border: none;
border-radius: var(--border-radius);
font-size: 1rem;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
display: flex;
align-items: center;
gap: 6px;
}
#searchBtn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(233, 69, 96, 0.4);
}
#searchBtn:active {
transform: translateY(0);
}
.error-message {
color: #ff6b6b;
font-size: 0.9rem;
margin-top: 8px;
min-height: 20px;
}
/* Weather Display Card */
.weather-display {
min-height: 200px;
}
.current-weather-card {
background: var(--bg-card);
border-radius: var(--border-radius);
padding: 30px;
box-shadow: var(--shadow);
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.city-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.city-name {
font-size: 1.8rem;
font-weight: 600;
}
.country-code {
font-size: 0.9rem;
color: var(--text-secondary);
background: rgba(255, 255, 255, 0.1);
padding: 4px 10px;
border-radius: 20px;
}
.weather-main {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 20px;
}
.weather-icon-large {
width: 100px;
height: 100px;
}
.temperature {
font-size: 4rem;
font-weight: 300;
line-height: 1;
}
.temperature span {
font-size: 1.5rem;
color: var(--text-secondary);
}
.weather-description {
font-size: 1.2rem;
text-transform: capitalize;
color: var(--text-secondary);
}
.weather-details {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: 16px;
margin-top: 20px;
}
.detail-item {
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
padding: 14px;
text-align: center;
}
.detail-label {
font-size: 0.75rem;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 1px;
}
.detail-value {
font-size: 1.3rem;
font-weight: 600;
margin-top: 6px;
}
/* Forecast Section */
.forecast-section h2 {
font-size: 1.4rem;
margin-bottom: 16px;
color: var(--text-secondary);
}
.forecast-cards {
display: flex;
gap: 12px;
overflow-x: auto;
padding-bottom: 10px;
scroll-behavior: smooth;
}
.forecast-cards::-webkit-scrollbar {
height: 6px;
}
.forecast-cards::-webkit-scrollbar-thumb {
background: var(--accent);
border-radius: 3px;
}
.forecast-card {
min-width: 140px;
background: var(--bg-card);
border-radius: var(--border-radius);
padding: 20px 16px;
text-align: center;
flex-shrink: 0;
box-shadow: var(--shadow);
transition: transform 0.2s;
}
.forecast-card:hover {
transform: translateY(-4px);
}
.forecast-date {
font-size: 0.85rem;
color: var(--text-secondary);
margin-bottom: 8px;
}
.forecast-icon {
width: 50px;
height: 50px;
margin: 0 auto;
}
.forecast-temp {
font-size: 1.4rem;
font-weight: 600;
margin: 8px 0;
}
.forecast-desc {
font-size: 0.8rem;
color: var(--text-secondary);
text-transform: capitalize;
}
/* Loading Spinner */
.loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 0;
color: var(--text-secondary);
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.1);
border-top: 4px solid var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
margin-bottom: 16px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.hidden {
display: none;
}
/* Responsive */
@media (max-width: 600px) {
header h1 {
font-size: 1.8rem;
}
.temperature {
font-size: 3rem;
}
.weather-details {
grid-template-columns: repeat(2, 1fr);
}
#searchForm {
flex-direction: column;
}
#searchBtn {
justify-content: center;
}
}
JavaScript — Fetching and Displaying Weather Data
The JavaScript handles API communication, DOM manipulation, error handling, and data processing. We use the Fetch API for HTTP requests and template literals for clean HTML generation. The code is structured into clear, single-responsibility functions.
// API Configuration — Replace with your actual API key
const API_KEY = 'YOUR_OPENWEATHERMAP_API_KEY_HERE';
const BASE_URL = 'https://api.openweathermap.org/data/2.5';
// DOM Element References
const searchForm = document.getElementById('searchForm');
const cityInput = document.getElementById('cityInput');
const weatherDisplay = document.getElementById('weatherDisplay');
const forecastCards = document.getElementById('forecastCards');
const errorMsg = document.getElementById('errorMsg');
const loadingSpinner = document.getElementById('loadingSpinner');
const forecastSection = document.getElementById('forecastSection');
/**
* Fetches current weather data for a given city
* @param {string} city - City name to search for
* @returns {Promise
Complete Weather App — All-in-One HTML File
For convenience, here is the entire weather app combined into a single HTML file. Simply replace YOUR_OPENWEATHERMAP_API_KEY_HERE with your actual API key, save the file as index.html, and open it in a browser.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WeatherVue - Weather App</title>
<style>
/* === All CSS from the CSS section above goes here === */
:root {
--bg-primary: #1a1a2e;
--bg-secondary: #16213e;
--bg-card: #0f3460;
--accent: #e94560;
--text-primary: #ffffff;
--text-secondary: #a0a0b0;
--border-radius: 12px;
--shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
color: var(--text-primary);
min-height: 100vh;
display: flex;
justify-content: center;
padding: 20px;
}
.container {
max-width: 800px;
width: 100%;
}
header {
text-align: center;
margin-bottom: 30px;
}
header h1 {
font-size: 2.5rem;
background: linear-gradient(135deg, #e94560, #ff6b6b);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
header p {
color: var(--text-secondary);
font-size: 1rem;
margin-top: 8px;
}
.search-section {
margin-bottom: 30px;
}
#searchForm {
display: flex;
gap: 10px;
}
#cityInput {
flex: 1;
padding: 14px 18px;
border: 2px solid rgba(255, 255, 255, 0.1);
border-radius: var(--border-radius);
background: rgba(255, 255, 255, 0.05);
color: var(--text-primary);
font-size: 1rem;
outline: none;
transition: border-color 0.3s, box-shadow 0.3s;
}
#cityInput:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(233, 69, 96, 0.2);
}
#searchBtn {
padding: 14px 24px;
background: var(--accent);
color: white;
border: none;
border-radius: var(--border-radius);
font-size: 1rem;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
display: flex;
align-items: center;
gap: 6px;
}
#searchBtn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(233, 69, 96, 0.4);
}
#searchBtn:active {
transform: translateY(0);
}
.error-message {
color: #ff6b6b;
font-size: 0.9rem;
margin-top: 8px;
min-height: 20px;
cursor: pointer;
}
.weather-display {
min-height: 200px;
}
.current-weather-card {
background: var(--bg-card);
border-radius: var(--border-radius);
padding: 30px;
box-shadow: var(--shadow);
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translate