Hex Version Hex Docs License Total Download Last Updated

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"}
  ]
end

Upgrading 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)}")
end

Available 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
end

Event Metadata

[:ex_owm, :request, :start]:

  • endpoint - API endpoint (:current_weather, :one_call, etc.)
  • location - Location struct or map
  • cache - :hit or :miss

[:ex_owm, :request, :stop]:

  • All metadata from :start event
  • result - The API result tuple
  • duration - 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, false

Default TTL by endpoint:

  • current_weather: 10 minutes
  • one_call: 10 minutes
  • forecast_5day: 1 hour
  • forecast_hourly: 1 hour
  • forecast_16day: 6 hours
  • historical: 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_stream for 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.