Foreca Weather API · Documentation
JavaScript Weather API Quickstart
Fetch an hourly forecast with Node.js, then show it and a weather map in a web page through a small server of your own, so your key never reaches the browser.
Before you start
You need Node.js 18 or later, which has fetch built in, and an API key. The Quickstart shows how to get a key.
export FORECA_API_KEY="paste-your-key-here"
In Windows PowerShell, set the key with $env:FORECA_API_KEY = "paste-your-key-here".
Node.js
Save this as forecast.mjs. The .mjs ending lets it use await at the top level.
const API = 'https://weatherapi.foreca.net'
const headers = { Authorization: `Bearer ${process.env.FORECA_API_KEY}` }
const res = await fetch(`${API}/api/v1/forecast/hourly/24.94,60.17?periods=12`, { headers })
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`)
const { forecast } = await res.json()
for (const hour of forecast) {
console.log(hour.time.slice(11, 16), `${hour.temperature} °C`, `wind ${hour.windSpeed} m/s`)
}
Run it with node forecast.mjs. It prints the time, temperature and wind for each of the next 12 hours.
In the browser, through your own server
Never put your key in code that runs in the browser: anyone who opens the page can read it and use it on your plan. Let a small server of your own call the API instead. The page asks your server, and only the server knows the key.
The server answers /forecast by calling the API with your key, and serves the page for everything else. Its /capabilities and /tiles routes are for the weather map below:
import { createServer } from 'node:http'
import { readFile } from 'node:fs/promises'
const API = 'https://weatherapi.foreca.net'
const headers = { Authorization: `Bearer ${process.env.FORECA_API_KEY}` }
async function forward(res, path) {
try {
const api = await fetch(API + path, { headers })
res.writeHead(api.status, {
'Content-Type': api.headers.get('content-type') ?? 'application/octet-stream',
'Cache-Control': 'max-age=300',
})
res.end(Buffer.from(await api.arrayBuffer()))
} catch (err) {
res.writeHead(502, { 'Content-Type': 'application/json' })
res.end(JSON.stringify({ message: err.message }))
}
}
createServer(async (req, res) => {
const url = new URL(req.url, 'http://localhost')
const tile = url.pathname.match(/^\/tiles\/(\d+)\/(\d+)\/(\d+)\/([\dTZ:-]+)\/(\d+)$/)
if (url.pathname === '/forecast') {
const location = url.searchParams.get('location') ?? '24.94,60.17'
return forward(res, `/api/v1/forecast/hourly/${encodeURIComponent(location)}?periods=12`)
}
if (url.pathname === '/capabilities') return forward(res, '/api/v1/capabilities')
if (tile) return forward(res, `/api/v1/image/tile/${tile.slice(1).join('/')}`)
const page = url.pathname === '/map' ? './map.html' : './index.html'
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
res.end(await readFile(new URL(page, import.meta.url)))
}).listen(8080, () => console.log('Open http://localhost:8080'))
The page sits next to it, fetches /forecast and lists the hours:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Hourly forecast</title>
<h1>Helsinki, next 12 hours</h1>
<ul id="forecast"></ul>
<script type="module">
const list = document.getElementById('forecast')
const res = await fetch('/forecast?location=24.94,60.17')
if (!res.ok) {
list.textContent = await res.text()
} else {
const { forecast } = await res.json()
for (const hour of forecast) {
const item = document.createElement('li')
item.textContent = `${hour.time.slice(11, 16)} ${hour.temperature} °C, wind ${hour.windSpeed} m/s`
list.append(item)
}
}
</script>
Start it with node server.mjs and open http://localhost:8080. In production, let the server forward only the requests your page needs, and cache the answers for up to 15 minutes: in Europe and the continental USA the first hours of a forecast are updated about that often, the rest a few times a day.
A weather map in the browser
Map tiles need the key too, and a map library fetches them from the user's device. So the same server forwards two more requests: /capabilities, which lists each layer's available times, and /tiles/…, which returns one tile. It forwards only tile requests in the shape the map asks for, so the server cannot be used to call the rest of the API on your key.
Save this next to the server as map.html. It draws an OpenStreetMap base map with Leaflet and lays the temperature layer over it at the current time:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Weather map</title>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css">
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
<div id="map" style="height: 100vh"></div>
<script type="module">
const LAYER = 2
const map = L.map('map').setView([60.17, 24.94], 5)
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
}).addTo(map)
const { images } = await (await fetch('/capabilities')).json()
const layer = images.find((image) => image.id === LAYER)
const time = layer.times.available[layer.times.current]
L.tileLayer(`/tiles/{z}/{x}/{y}/${time}/${LAYER}`, {
opacity: 0.7,
attribution: ['Foreca', layer.attribution].filter(Boolean).join(' · '),
}).addTo(map)
</script>
Open http://localhost:8080/map. Each tile counts as one request, so the server lets the browser keep tiles for five minutes. Change LAYER to show radar, satellite or another layer: Weather Maps lists every layer id with its coverage. Show the attribution the map draws in the corner; it names the data source when a layer has one.
Errors
Both examples pass an error status on with the API's JSON body, for example 401: {"error":"invalid_token",…}. When a request fails lists what each error means. On 429, wait the number of seconds in the Retry-After header before you retry.
Next steps
- Quickstart: the same requests with curl, and how to read the response.
- Point Forecasts reference: every parameter and response field of the current, hourly and daily forecasts.
- Weather icons and symbol codes: what each symbol code means, and icon sets to download.
- Usage endpoint: how many requests your key has used.