---
title: Generate synthetic content
summary: >-
  You need to fetch personalized data from external APIs and serve it to the
  user without a full web stack.
url: >-
  https://www.fastly.com/documentation/solutions/tutorials/compute/generate-synthetic-content
---

While the view layer of the web consists of HTML, CSS, and JavaScript, much of the data you want to present to your users is better represented and exchanged using formats like JSON. Generally, data is interpolated into a template within the core infrastructure of an application and then the HTML can be cached at the edge. In this tutorial you can learn how to move the templating process to the edge, allowing your core application to focus on pure data.

This tutorial documents the creation of a fast, personalized weather dashboard powered by Fastly's geolocation API and [WeatherAPI](https://www.weatherapi.com/), a provider of weather data in partnership with governments and meteorological agencies around the world. Later in the tutorial, you will create a free WeatherAPI account and get your own API token to retrieve weather data.

You can see below exactly what you will be building:

<iframe src="https://weather-demo.edgecompute.app" width="100%" height="600px" sandbox="allow-same-origin allow-scripts allow-forms"/>

## WeatherAPI.com

WeatherAPI provides the [Forecast API](https://www.weatherapi.com/api-explorer.aspx#forecast) endpoint where you can send requests with the latitude and longitude coordinates of any place in the world, and receive JSON-encoded weather information for that location. This is ideal for a basic weather app, and can be combined with the [Fastly geolocation API](https://docs.rs/fastly/0.9.1/fastly/geo/fn.geo_lookup.html) to fetch weather information for the location associated with the end user's IP address.

You will integrate with this later, but for now let's look at an example request and response from the Forecast endpoint:

### Current weather data

```term
curl -L -X GET 'https://api.weatherapi.com/v1/forecast.json?q=51.521,-0.136&days=4&key=4f6b298d55504374b78203319220712'
```

```json context="GET api.weatherapi.com/v1/forecast.json?q={lat},{long}&days={n}&key={api_key}" truncate
{
    "location": {
        "name": "London",
        "region": "City of London, Greater London",
        "country": "United Kingdom",
        "lat": 51.52,
        "lon": -0.14,
        "tz_id": "Europe/London",
        "localtime_epoch": 1670523898,
        "localtime": "2022-12-08 18:24"
    },
    "current": {
        "last_updated_epoch": 1670523300,
        "last_updated": "2022-12-08 18:15",
        "temp_c": 2.0,
        "temp_f": 35.6,
        "is_day": 0,
        "condition": {
            "text": "Clear",
            "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png",
            "code": 1000
        },
        "wind_mph": 4.3,
        "wind_kph": 6.8,
        "wind_degree": 300,
        "wind_dir": "WNW",
        "pressure_mb": 1010.0,
        "pressure_in": 29.83,
        "precip_mm": 0.0,
        "precip_in": 0.0,
        "humidity": 75,
        "cloud": 0,
        "feelslike_c": 0.1,
        "feelslike_f": 32.2,
        "vis_km": 10.0,
        "vis_miles": 6.0,
        "uv": 1.0,
        "gust_mph": 4.7,
        "gust_kph": 7.6
    },
    "forecast": {
        "forecastday": [
            {
                "date": "2022-12-08",
                "date_epoch": 1670457600,
                "day": {
                    "maxtemp_c": 4.5,
                    "maxtemp_f": 40.1,
                    "mintemp_c": 0.5,
                    "mintemp_f": 32.9,
                    "avgtemp_c": 2.2,
                    "avgtemp_f": 36.0,
                    "maxwind_mph": 5.6,
                    "maxwind_kph": 9.0,
                    "totalprecip_mm": 0.0,
                    "totalprecip_in": 0.0,
                    "totalsnow_cm": 0.0,
                    "avgvis_km": 10.0,
                    "avgvis_miles": 6.0,
                    "avghumidity": 69.0,
                    "daily_will_it_rain": 0,
                    "daily_chance_of_rain": 0,
                    "daily_will_it_snow": 0,
                    "daily_chance_of_snow": 0,
                    "condition": {
                        "text": "Sunny",
                        "icon": "//cdn.weatherapi.com/weather/64x64/day/113.png",
                        "code": 1000
                    },
                    "uv": 2.0
                },
                "astro": {
                    "sunrise": "07:53 AM",
                    "sunset": "03:52 PM",
                    "moonrise": "03:41 PM",
                    "moonset": "08:29 AM",
                    "moon_phase": "Full Moon",
                    "moon_illumination": "100"
                },
                "hour": [
                    {
                        "time_epoch": 1670457600,
                        "time": "2022-12-08 00:00",
                        "temp_c": 1.5,
                        "temp_f": 34.7,
                        "is_day": 0,
                        "condition": {
                            "text": "Clear",
                            "icon": "//cdn.weatherapi.com/weather/64x64/night/113.png",
                            "code": 1000
                        },
                        "wind_mph": 4.0,
                        "wind_kph": 6.5,
                        "wind_degree": 289,
                        "wind_dir": "WNW",
                        "pressure_mb": 1016.0,
                        "pressure_in": 29.99,
                        "precip_mm": 0.0,
                        "precip_in": 0.0,
                        "humidity": 77,
                        "cloud": 9,
                        "feelslike_c": -0.5,
                        "feelslike_f": 31.1,
                        "windchill_c": -0.5,
                        "windchill_f": 31.1,
                        "heatindex_c": 1.5,
                        "heatindex_f": 34.7,
                        "dewpoint_c": -2.1,
                        "dewpoint_f": 28.2,
                        "will_it_rain": 0,
                        "chance_of_rain": 0,
                        "will_it_snow": 0,
                        "chance_of_snow": 0,
                        "vis_km": 10.0,
                        "vis_miles": 6.0,
                        "gust_mph": 6.0,
                        "gust_kph": 9.7,
                        "uv": 1.0
                    },
                    ...
                ]
            },
            ...
        ]
    }
}
```

## Instructions

> **IMPORTANT:** This tutorial assumes that you have already have the [Fastly CLI](https://www.fastly.com/documentation/reference/cli/) installed. If you are new to the platform, read our [Getting Started](https://www.fastly.com/documentation/guides/compute/getting-started-with-compute/) guide.

### Initialize a project

If you haven't already created a Rust-based Compute project, run <kbd>fastly compute init</kbd> in a new directory in your terminal and follow the prompts to provision a new service using the [empty Rust starter kit](https://www.fastly.com/documentation/solutions/starters/compute-starter-kit-rust-empty/):

```term
$ mkdir weather && cd weather
$ fastly compute init

Creating a new Compute project.

Press ^C at any time to quit.

Name: [weather]
Description: A weather dashboard served at the edge
Author: You!
Language:
[1] Rust
[2] JavaScript
[3] Go
[5] Other ('bring your own' Wasm binary)
Choose option: [1]
Starter kit:
[1] Default starter for Rust
    A basic starter kit that demonstrates routing, simple synthetic responses and
    overriding caching rules.
    https://github.com/fastly/compute-starter-kit-rust-default
[2] Empty starter for Rust
    An empty starter kit project template.
    https://github.com/fastly/compute-starter-kit-rust-empty
...
Choose option or paste git URL: [2]
```

### Install dependencies

When the JSON response comes back from WeatherAPI, you'll need something to parse it so that you can use the values in your template. [`serde`](https://docs.rs/serde) is a Rust crate that provides serialization and deserialization for common data formats like JSON.

To generate the view that will be sent to the client, you will need a templating library. For the purposes of this tutorial we suggest [`tinytemplate`](https://docs.rs/tinytemplate).

Add these dependencies to your `Cargo.toml` file:

You will also need a helper data structure specific to this tutorial. We have packaged this into a [Rust crate](https://crates.io/crates/weather_demo_helpers) that you can include in your project:

### Use the WeatherAPI backend

#### Obtain an API token

To use the free plan of WeatherAPI, you will need an account and API key. Head to the [sign up page](https://www.weatherapi.com/signup.aspx) and enter your details. Follow their instructions to activate your account.

Head to your [dashboard](https://www.weatherapi.com/my/) and copy the API key that has been generated for you. You will need to add this key as a property named `key` to a dictionary (also known as a Compute Config Store) named `weather_auth` on your Fastly service, which you will use in the program next to securely fetch the API token. You can do this however you prefer:

- Via the [web interface](https://www.fastly.com/documentation/guides/full-site-delivery/dictionaries/working-with-dictionaries/#creating-a-dictionary-via-the-web-interface)
- Via the [API](https://www.fastly.com/documentation/reference/api/dictionaries/)
- Via the [CLI](https://www.fastly.com/documentation/reference/cli), using the <kbd>fastly dictionary create</kbd> and <kbd>fastly dictionary-item create</kbd> commands

### Fetch the API key

Remove the contents of the `main` method generated by the [empty starter kit](https://www.fastly.com/documentation/solutions/starters/compute-starter-kit-rust-empty), and write a method to retrieve the API key you added to the Config Store (dictionary) earlier:

You will use this method later to generate an API request to WeatherAPI.

### Match the request route

This tutorial will serve the generated HTML, stylesheets, and images from the same Compute service. To achieve this, you can use Rust's [pattern syntax](https://doc.rust-lang.org/book/ch18-03-pattern-syntax.html) to execute different logic based on properties of the incoming request, such as the path. Write some basic routing code inside your now-empty `main` function:

### Fetch the weather data for the user's location

Using Fastly's geolocation API to fetch the user's latitude and longitude will give you all the data needed to construct a request to WeatherAPI. Add the imports and `BACKEND_NAME` constant as shown below, and the logic within the `"/"` branch of the match clause to make a request to the backend you defined earlier:

If you look at the [JSON response](https://www.fastly.com/documentation/solutions/tutorials/compute/generate-synthetic-content#current-weather-data) from WeatherAPI, you'll see that it contains a lot of data you don't need for the weather dashboard – and also that it doesn't contain some elements you _do_ need, like the names of weekdays, or today's date in a friendly format.

> 💡 The [`take_response_body`](https://docs.rs/fastly/latest/fastly/http/struct.Response.html#method.take_body_json) method of the Fastly [`Response`](https://docs.rs/fastly/latest/fastly/http/struct.Response.html) struct takes a custom return type which can be used to deserialize the JSON response body. You can either define your own type to represent the data you need for the dashboard, or use one from the [`weather_demo_helpers`](https://crates.io/crates/weather_demo_helpers) crate that accompanies this tutorial, to save time.

Import the `ForecastAPIResponse` struct from the helper crate, and pass it to the `take_response_body` method:

> 👉 Under the hood, [`ForecastAPIResponse`](https://docs.rs/weather_demo_helpers/0.1.1/src/weather_demo_helpers/lib.rs.html#83-125) performs sophisticated transformations on the JSON response – for example, by converting timestamps to weekday names, or weather conditions to the names of [Material symbols](https://fonts.google.com/icons) – thanks to the [`serde`](https://serde.rs/) framework.
> The final data structure contains all the variables you need to power your dashboard:
>
> ```rust truncate
> ForecastAPIResponse {
>     at: LocationData {
>         location: "London",
>         dt: DateElements {
>             weekday: "Thursday",
>             weekday_short: "Thu",
>             pretty_date: "8 December 2022",
>         },
>     },
>     current: CurrentWeatherData {
>         temp_c: "2.0",
>         temp_f: "35.6",
>         wind_mph: "4.3",
>         wind_kph: "6.8",
>         precip_mm: "0.0",
>         precip_in: "0.0",
>         humidity: 75,
>         condition: WeatherCondition {
>             description: "Clear",
>             symbol: "sunny",
>         },
>     },
>     forecast: ForecastData {
>         at: [
>             WeatherReport {
>                 dt: DateElements {
>                     weekday: "Thursday",
>                     weekday_short: "Thu",
>                     pretty_date: "8 December 2022",
>                 },
>                 day: DayWeatherData {
>                     avgtemp_c: "2.2",
>                     avgtemp_f: "36.0",
>                     condition: WeatherCondition {
>                         description: "Sunny",
>                         symbol: "sunny",
>                     },
>                 },
>             },
>             ...
>         ],
>     },
> }
> ```

At this point, visiting your application's domain will fetch your weather info, but will not yet return it to your browser. You'll fix this in the next step.

### Generate HTML using a template

This tutorial uses the [`tinytemplate`](https://docs.rs/tinytemplate/1.2.0/tinytemplate/) crate to interpolate dynamic weather data into a static HTML template. You'll need to define a serializable type for the data you will pass to the template, and a method to generate HTML from the data.

If you'd like to use the `static/index.html` template and other assets from this example, download [static.zip](https://www.fastly.com/documentation/downloads/tutorial-synthetic/static.zip) and extract it in the `src` folder of your project. Otherwise, feel free to build your own frontend starting from the `TemplateContext` data structure.

### Serve static assets

Your dashboard should now load and show your current weather! It doesn't look that good though, so you will need to set up a route to serve your stylesheet:

Next, set up some routes to serve the background images from the asset bundle, and avoid repetition by writing a function that constructs image responses:

### Switching units

Your Compute service will be deployed globally. Some users will expect to read temperatures in °C (the metric scale), and others in °F. It's a good idea to default to a scale based on the end user's location, but also to provide a way to switch units.

To achieve this, you can read a query string parameter, `metric`, to determine the desired scale based on user input. If this parameter is not present:

1. Use the [`country_code`](https://docs.rs/fastly/latest/fastly/geo/struct.Geo.html#method.country_code) method from Fastly's geolocation API to get the 2-letter country code associated with the user's IP address.
2. Unless the user is in the United States, the Bahamas, the Cayman Islands, Liberia, Palau, Micronesia, or the Marshall Islands, default to the metric scale to display temperatures.

The "Switch units" button in the example template should now work as intended. Clicking on this button will reload the page with the `metric` query parameter set to the opposite of its current value.

### Build and deploy

Congratulations! You now have an elegant weather dashboard that can run at the edge. If you haven't yet, run <kbd>fastly compute publish</kbd> to build and deploy your service to the Fastly network. When prompted, enter `api.weatherapi.com` as the backend hostname:

```term
$ fastly compute publish
✓ Initializing...
✓ Verifying package manifest...
✓ Verifying local rust toolchain...
✓ Building package using rust toolchain...
✓ Creating package archive...

SUCCESS: Built rust package weather (pkg/weather.tar.gz)

There is no Fastly service associated with this package. To connect to an existing service
add the Service ID to the fastly.toml file, otherwise follow the prompts to create a
service now.

Press ^C at any time to quit.

Create new service: [y/N] y

✓ Initializing...
✓ Creating service...

Domain: [random-funky-words.edgecompute.app]

Backend (hostname or IP address, or leave blank to stop adding backends): api.weatherapi.com
Backend port number: [80] 443
Backend name: [backend_1] weather_api

Backend (hostname or IP address, or leave blank to stop adding backends):

✓ Initializing...
✓ Creating domain 'random-funky-words.edgecompute.app'...
✓ Creating backend 'weather_api' (host: api.weatherapi.com, port: 443)...
✓ Uploading package...
✓ Activating version...

Manage this service at:
        https://manage.fastly.com/configure/services/PS1Z4isxPaoZGVKVdv0eY

View this service at:
        https://random-funky-words.edgecompute.app

SUCCESS: Deployed package (service PS1Z4isxPaoZGVKVdv0eY, version 1)
```

## Next Steps

Now that you understand how to make your program act on [geolocation data](https://docs.rs/fastly/0.10.2/fastly/geo/struct.Geo.html), why not use this to select an appropriate default unit based on the user's locale?

You could also try improving the logging provided in the tutorial, using the [`log-fastly` crate](https://docs.rs/log-fastly). This allows you to log data to any of Fastly's [supported logging backends](https://www.fastly.com/documentation/guides/integrations/non-fastly-services/developer-guide-logging).
