Setting Up WeatherKit REST API In NodeJS

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.

Current Weather: London
12
15
Wind
2
UV
50%
Cloud
Weather data provided by Apple WeatherKit

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">&#8451;</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 😊

  • About
  • Blog
  • Privacy
Looking to email me? You can get me on my first name at allthecode.co