WeatherKit is Apple's new weather data service, announced at WWDC 2022 it is the their answer to privacy focussed weather data. Not only have they created this for use in native iOS Swift apps but they have also created a REST API so that web developers are able to access it as well.
In fact, it's where this widget is getting its weather data from - if you reload the page it might change depending on how turbulent London's weather is right now.
The WeatherKit REST API Documentation is fairly limited on how to get this set up so here is a guide to get up you up and running with Apple's WeatherKit. In fact, I had a labs call with two Apple engineers to get this working which was pretty cool. We will build a simple ExpressJS server and a index.html page to display weather for a set location.
UPDATE: Apple have since updated their documentation on this topic including adding a page on the JWT creation that was such a pain before.
WeatherKit REST API setup
The REST API requires a signed JWT to be attached to each request. To set this up you need:
- A paid developer account
- An App Identifier
- A key
You can set up an App Identifier on the Identifiers page of your developer account. You need to create a new App ID
of type App, give it a Bundle ID
in reverse-domain name style, so com.myapp.weather
or similar, and then make sure you select WeatherKit from the App Services tab. This App Identifier can take about 30 minutes to propagate through Apple's systems.
For the key you go to the Keys page in your developer account and add a new key with WeatherKit selected. Remember to download the key file you get at the end!
WeatherKit REST API JWT
Now you have your key and App Identifier we can create a server to sign that key. You will be signing the JWT yourself so you will need a backend/server where you can execute your code, as always do not expose your key to the browser or client
So, let's create a little ExpressJS server. Comments in the code explain what is going on. I'm assuming you know how to use ExpressJS already.
const express = require("express");
const axios = require("axios");
const fs = require("fs");
const jwt = require("jsonwebtoken");
var cors = require("cors");
const app = express();
const port = 3000;
app.use(cors());
app.get("/", async (req, res) => {
// This is the key file you downloaded from Apple
var privateKey = fs.readFileSync("YOUR KEY FILE FROM DEVELOPER CENTER.p8");
// Creating the signed token needs specific data
var token = jwt.sign(
{
sub: "APP ID", // the reverse URL App Id you made above
},
privateKey,
{
issuer: "TEAM ID", // find your TeamID in your developer account
expiresIn: "1h", // give it 1 hour of validity
keyid: "KEY ID", // this is the ID for the key you created
algorithm: "ES256", // this is the algorithm Apple used
header: {
// see details below for this
id: "YOUR TEAM ID.YOUR APP ID",
},
}
);
// log your token so you can decode it and manually check it's ok, more below on this
//console.log(token);
const url =
"https://weatherkit.apple.com/api/v1/weather/en/51.677612/-2.937941?dataSets=currentWeather&timezone=Europe/London";
// add the token to your headers
const config = {
headers: { Authorization: `Bearer ${token}` },
};
// get the data
const { data: weatherData } = await axios.get(url, config);
// prove we have data
//console.log(weatherData);
// return the data to your front end
res.json(weatherData);
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
Your decoded JWT needs to look like this:
{
"header": {
"alg": "ES256",
"typ": "JWT",
"kid": "KEY_ID",
"id": "TEAM_ID.APP_ID" // this is the bit that has been tricking people up
},
"payload": {
"subj": "APP_ID",
"iat": 1654823790,
"exp": 1654827390,
"iss": "TEAM_ID",
},
"signature": "D108jNki1tXX3mSYOAPp-Wpds8kEbGJ-lhKPtasdfSqoDyiJ2WbeI6U73UWCYWQgISlDOzaXdI5rSrSFcrg5g"
}
To get id
in the header I needed to set it manually when creating the JWT, Apple is looking for id
in the header and depending on your JWT signing library of choice that may not (probably won't) be there by default.
header: {
id: "YOUR TEAM ID.YOUR APP ID", // see details below for this
},
WeatherKit REST API Front end
To call the serve end point you can use the code below in an index.html
to make a simple front end that starts with default data and then adds in the data from weather kit.
I've included Axios with a CDN link on line 5 and then line 11 pulls in TailwindCSS via a CDN as well for quick and easy styling and explains why the HTML tags have so many classes on them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script
src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.0.0-alpha.1/axios.min.js"
integrity="sha512-xIPqqrfvUAc/Cspuj7Bq0UtHNo/5qkdyngx6Vwt+tmbvTLDszzXM0G6c91LXmGrRx8KEPulT+AfOOez+TeVylg=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script src="https://cdn.tailwindcss.com"></script>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Weather Kit Example</title>
</head>
<body>
<div class="container max-w-3xl mx-auto p-8">
<h1 class="text-3xl text-center font-bold text-gray-700">All The Code</h1>
<div
class="shadow-lg shadow-black/40 w-[400px] p-4 mx-auto mt-10 rounded flex flex-col gap-y-6"
>
<h2 class="text-xl font-bold text-center">Current Weather</h2>
<div class="text-7xl font-black text-center">
<span id="temperature">0</span> <span class="font-thin">℃</span>
</div>
<div class="flex flex-row justify-between">
<div class="flex flex-col text-center w-1/3">
<div id="wind" class="text-3xl font-bold">17.5</div>
<div class="text-3xl">Wind</div>
</div>
<div class="flex flex-col text-center w-1/3">
<div id="uv" class="text-3xl font-bold">0</div>
<div class="text-3xl">UV</div>
</div>
<div class="flex flex-col text-center w-1/3">
<div class="text-3xl font-bold"><span id="cloud">81</span>%</div>
<div class="text-3xl">Cloud</div>
</div>
</div>
</div>
<div class="text-center mt-10">
Weather data provided by
<a href="https://weather-data.apple.com/legal-attribution.html"
>Apple WeatherKit</a
>
</div>
</div>
</body>
<script>
// add the below script in here
</script>
</html>
The JavaScript to get the data
axios
.get("http://localhost:3000", {
headers: { "Access-Control-Allow-Origin": "*" },
})
.then((res) => {
let temp = document.getElementById("temperature");
temp.innerHTML = res.data.currentWeather.temperature;
if (parseFloat(res.data.currentWeather.temperature) < 15) {
temp.classList.add("text-blue-500");
} else {
temp.classList.add("text-red-500");
}
document.getElementById("wind").innerHTML =
res.data.currentWeather.windSpeed;
document.getElementById("uv").innerHTML = res.data.currentWeather.uvIndex;
document.getElementById("cloud").innerHTML =
res.data.currentWeather.cloudCover * 100;
});
When this page loads it will request weather data from Apple via your server with a sign JWT 😊