Modern, fast OpenWeatherMap API client for Elixir applications.
Features
Clean, intuitive API with validated location types
Concurrent requests with automatic batching
Smart caching with configurable TTL per endpoint
Telemetry integration for production observability
Modern dependencies (Req, Nebulex)
Comprehensive error handling with detailed feedback
Installation
Add ExOwm as a dependency to your mix.exs file:
defp deps() do
[
{:ex_owm, "~> 2.0"}
]
endUpgrading from 1.x?
See UPGRADE.md for a comprehensive migration guide. The v1.x API still works with deprecation warnings.
Configuration
To use OWM APIs, you need to register for an account (free plan is available) and obtain an API key.
Add the following configuration to your config/config.exs file:
config :ex_owm, api_key: System.get_env("OWM_API_KEY")
# Optional: Disable caching (enabled by default)
config :ex_owm, :cache_enabled, false
# Optional: Configure cache TTL per endpoint (when cache is enabled)
config :ex_owm, :default_ttl, :timer.minutes(10)
config :ex_owm, :current_weather_ttl, :timer.minutes(5)
config :ex_owm, :forecast_5day_ttl, :timer.hours(1)Quick Start
Basic Usage
# Using validated location constructors (recommended)
location = ExOwm.Location.by_city("Warsaw")
{:ok, weather} = ExOwm.current_weather(location, units: :metric)
# Using raw maps (backward compatible)
{:ok, weather} = ExOwm.current_weather(%{city: "Warsaw"}, units: :metric)
# Multiple location types
location = ExOwm.Location.by_city("London", country: "uk")
location = ExOwm.Location.by_coords(52.374031, 4.88969)
location = ExOwm.Location.by_id(2759794)
location = ExOwm.Location.by_zip("94040", country: "us")Batch Requests
locations = [
ExOwm.Location.by_city("Warsaw"),
ExOwm.Location.by_city("London", country: "uk"),
ExOwm.Location.by_coords(52.374031, 4.88969)
]
results = ExOwm.current_weather_batch(locations, units: :metric, lang: :pl)
# => [
# {:ok, %{"name" => "Warszawa", ...}},
# {:ok, %{"name" => "London", ...}},
# {:ok, %{"name" => "Amsterdam", ...}}
# ]Error Handling
location = ExOwm.Location.by_city("InvalidCity")
case ExOwm.current_weather(location) do
{:ok, data} ->
IO.inspect(data)
{:error, :not_found, details} ->
IO.puts("City not found: #{inspect(details)}")
{:error, :api_key_invalid, _} ->
IO.puts("Invalid API key")
{:error, reason} ->
IO.puts("Request failed: #{inspect(reason)}")
endAvailable Endpoints
ExOwm supports the following OpenWeatherMap APIs:
Weather Data
Current Weather
# Single location
ExOwm.current_weather(location, units: :metric)
# Batch
ExOwm.current_weather_batch(locations, units: :metric)One Call API
Includes current weather, minute/hourly/daily forecasts, and historical data in one call.
# Requires coordinates
location = ExOwm.Location.by_coords(52.374031, 4.88969)
ExOwm.one_call(location, units: :metric)
# Batch
ExOwm.one_call_batch(locations, units: :metric)5 Day / 3 Hour Forecast
ExOwm.forecast_5day(location, units: :metric)
ExOwm.forecast_5day_batch(locations, units: :metric)4 Day Hourly Forecast
Note: Requires paid OpenWeatherMap plan.
ExOwm.forecast_hourly(location, units: :metric)
ExOwm.forecast_hourly_batch(locations, units: :metric)1-16 Day Daily Forecast
ExOwm.forecast_16day(location, cnt: 16, units: :metric)
ExOwm.forecast_16day_batch(locations, cnt: 16, units: :metric)Historical Weather Data
Available for the last 5 days.
yesterday = System.system_time(:second) - 86400
location =
ExOwm.Location.by_coords(52.374031, 4.88969)
|> ExOwm.Location.with_timestamp(yesterday)
ExOwm.historical(location, units: :metric)Air Quality
Current Air Pollution
Get current air quality index and pollutant concentrations (CO, NO, NO2, O3, SO2, NH3, PM2.5, PM10).
location = ExOwm.Location.by_coords(52.374031, 4.88969)
{:ok, data} = ExOwm.air_pollution(location)
# AQI scale: 1 = Good, 2 = Fair, 3 = Moderate, 4 = Poor, 5 = Very Poor
aqi = get_in(data, ["list", Access.at(0), "main", "aqi"])
ExOwm.air_pollution_batch(locations)Air Pollution Forecast
Four-day hourly forecast of air quality.
ExOwm.air_pollution_forecast(location)
ExOwm.air_pollution_forecast_batch(locations)Historical Air Pollution
Historical air quality data from November 27, 2020 onwards.
now = System.system_time(:second)
yesterday = now - 86_400
ExOwm.air_pollution_history(location, start: yesterday, end: now)
ExOwm.air_pollution_history_batch(locations, start: yesterday, end: now)Geocoding
Direct Geocoding
Convert location names to coordinates.
{:ok, locations} = ExOwm.geocode("London", limit: 5)
{:ok, locations} = ExOwm.geocode("London,GB", limit: 1)
# Returns: [%{"name" => "London", "lat" => 51.5074, "lon" => -0.1278, "country" => "GB"}]Reverse Geocoding
Convert coordinates to location names.
{:ok, locations} = ExOwm.reverse_geocode(52.374031, 4.88969, limit: 1)
# Returns: [%{"name" => "Amsterdam", "lat" => 52.374031, "lon" => 4.88969, "country" => "NL"}]Request Options
Weather APIs:
:units - :metric, :imperial, or :standard (default Kelvin)
:lang - Language code as atom (:pl, :en, :ru, :de)
:cnt - Number of forecast days for 16-day forecast (1-16)
:ttl - Cache TTL in milliseconds (overrides default)
Geocoding APIs:
:limit - Max results (default 5)
Air pollution history:
:start - Start timestamp (Unix time, UTC)
:end - End timestamp (Unix time, UTC)
Telemetry
ExOwm emits telemetry events for observability:
# In your application.ex
:telemetry.attach_many(
"ex-owm-handler",
[
[:ex_owm, :request, :start],
[:ex_owm, :request, :stop]
],
&MyApp.Telemetry.handle_owm_event/4,
nil
)
defmodule MyApp.Telemetry do
require Logger
def handle_owm_event([:ex_owm, :request, :stop], measurements, metadata, _) do
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
Logger.info("OpenWeatherMap API call completed",
endpoint: metadata.endpoint,
cache: metadata.cache,
duration_ms: duration_ms
)
end
def handle_owm_event([:ex_owm, :request, :start], _, _, _), do: :ok
endEvent Metadata
[:ex_owm, :request, :start]:
endpoint- API endpoint (:current_weather,:one_call, etc.)location- Location struct or mapcache-:hitor:miss
[:ex_owm, :request, :stop]:
- All metadata from
:startevent result- The API result tupleduration- Request duration (native time units in measurements)
Caching
ExOwm includes optional built-in caching using Nebulex. Caching is enabled by default to reduce API costs and improve response times.
Disable caching:
config :ex_owm, :cache_enabled, falseDefault TTL by endpoint:
current_weather: 10 minutesone_call: 10 minutesforecast_5day: 1 hourforecast_hourly: 1 hourforecast_16day: 6 hourshistorical: 24 hours
Override in config:
config :ex_owm, :default_ttl, :timer.minutes(10)
config :ex_owm, :current_weather_ttl, :timer.minutes(5)Override per-request:
ExOwm.current_weather(location, ttl: :timer.minutes(1))When caching is disabled, all requests go directly to the OpenWeatherMap API without any caching layer.
Location Validation
Use ExOwm.Location constructors for early validation:
# City - validates non-empty string
location = ExOwm.Location.by_city("Warsaw")
location = ExOwm.Location.by_city("London", country: "uk")
# Coordinates - validates lat/lon ranges
location = ExOwm.Location.by_coords(52.374031, 4.88969)
# Raises ArgumentError if lat not in [-90, 90] or lon not in [-180, 180]
# City ID - validates positive integer
location = ExOwm.Location.by_id(2759794)
# ZIP code - requires country parameter
location = ExOwm.Location.by_zip("94040", country: "us")
# Add timestamp for historical queries
location =
ExOwm.Location.by_coords(52.37, 4.89)
|> ExOwm.Location.with_timestamp(1615546800)Running Tests
Tests require a valid OpenWeatherMap API key and are disabled by default:
# Set your API key
export OWM_API_KEY="your_key_here"
# Remove the :api_based_test exclusion in test/test_helper.exs
# Then run tests
mix test
Architecture
ExOwm v2.0 uses a simplified architecture:
- No coordinator GenServers - Direct function calls with
Task.async_streamfor batching - Smart caching - Nebulex with Shards backend
- Modern HTTP - Req client with built-in retry/telemetry support
- Telemetry events - Full observability for production monitoring
This results in ~400 fewer lines of code compared to v1.x while being more maintainable and performant.
API Limitations
Please note that with a standard (free) license/API key, you may be limited in:
- Number of requests per minute (60 calls/minute for free tier)
- Access to certain endpoints (hourly forecast, 16-day forecast)
Refer to OpenWeatherMap pricing plans for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is MIT licensed. Please see the LICENSE.md file for more details.