First Worker

Task

Enhance AcmeCorp's API to return additional data by putting a simple Worker in front of it.

You can find more Worker use cases in our Examples.

Why

  • Workers allow you to augment web traffic without modifying the origin server
  • They can also help you scale by offloading some compute from the origin server
  • And they let you leverage Cloudflare's global network for high availability and low-latency content delivery

Steps

We will be build a worker demonstrating how to augment API responses without modifying the origin. By the end of this exercise, you will have a better understanding of how to intercept requests and modify responses using Cloudflare Workers.

1. Create DNS record for API

First, add a DNS record for AcmeCorp's API by selecting Websites in Cloudflare dashboard and selecting your zone. Then under DNS select Add record and add the following:

Type: A
Name: api
IPv4 address: 4.157.169.241
Proxy: yes

The final result should look like this:

When confirmed, exit the zone configuration by clicking on the account name.

Now let's have a look at the /orders endpoint of that API and see what it returns:

$ curl -s https://api.cfdemolab-zone-xxx.cfdemolab.xyz/v1/orders/1
⚙️ nocopy
{
  "id": 1,
  "title": "Ustilo condico ducimus stella.",
  "createdBy": {
    "name": "Marcel Aufderhar"
  },
  "createdAt": {
    "name": "Mon Apr 22 2024 16:31:20 GMT+0000 (Coordinated Universal Time)"
  },
  "items": [
    {
      "id": 0,
      "productName": "Awesome Car",
      "productUuid": "bf36c9e3-d301-4aae-b6c2-2f9f50970213",
      "quantity": 6,
      "unitPrice": 209
    },
    {
      "id": 1,
      "productName": "Gorgeous Car",
      "productUuid": "7e48e77c-4f97-4e74-b43b-559879860acf",
      "quantity": 8,
      "unitPrice": 224
    }
  ],
  "archived": false,
  "success": true
}
ℹ️
You will notice that if you run the same command multiple times, you will get different data. That's ok, this is just a mock API, the data itself is generated randomly and isn't important. The structure of the data is what matters.

So we have an API that returns a list of items for a specific order, together with quantity and unit price. Now let's imagine this API is being integrated with a new system and you're faced with these requirements:

  1. The new system needs the API to also return total price of each item (unitPrice * quantity)
  2. There are existing systems that integrate with the API and are sensitive to data they receive, make sure the data structure remains the same for those

Let's see if we can solve this with a Worker!

2. Create a Worker

In your Cloudflare dashboard, navigate to Workers & Pages and create a new Worker:

Give the Worker a Name and Deploy it:

Once deployed, click Edit code.

And replace your Worker's code with the following and Deploy it again:

export default {
  async fetch(request, env, ctx) {
    /* helper function that inserts the total price for each item */
    function addTotals(data) {
      return {
        ...data,
        items:data.items.map((item) => ({
          ...item,
          totalPrice: item.unitPrice
        }))
      }
    }

    /* exit if the Worker isn't proxying the /orders endpoint */
    if(!request.url.includes('/orders/')) return new Response("Worker not invoked on /orders endpoint");

    /* to retain backwards compatibility, we'll only alter the output
    if a header X-Api-Preference with value "include_total" is present */
    const apiPreference = request.headers.get("X-Api-Preference");

    /* retrieve and parse data from the origin (AcmeCorp's API) */
    const response = await fetch(request.url);
    const originalData = await response.json();

    /* if the header is present, add the totals to the data, otherwise
    pass response from origin without change */
    let returnData;
    if (apiPreference === "include_total") {
      returnData = addTotals(originalData);
    } else {
      returnData = originalData;
    }
    return new Response(JSON.stringify(returnData, null, 2), {
      headers: { "Content-Type": "application/json" }
    });
  },
};

Simple in-line Worker that adds totalPrice to each item

3. Add route for Worker

As a last step, make sure this Worker gets executed on all /orders requests from your API by opening the Worker in dashboard and then adding a new route under Settings ‣ Triggers ‣ Routes ‣ Add route.

Select you Zone, add a route for api.<your_zone>/v1/orders/* and select Fail open.

This tells Cloudflare to send requests to the /orders endpoint to your Worker instead of sending them to the origin.

The Fail open sends requests to other endpoints directly to the origin, bypassing this Worker.

4. Test Worker

If you now issue the same request you did at the beginning, nothing changes and we retain the requested backwards compatibility.

You can verify that the request was actually handled by your Worker by opening the Logs tab and selecting Begin log stream:

If you now add a header X-Api-Preference:include_total to your request, you should see totalPrice being included with each item.

curl -s https://api.cfdemolab-zone-xxx.cfdemolab.xyz/v1/orders/1 -H "X-Api-Preference:include_total"
⚙️ nocopy
{
  "id": 1,
  "title": "Angustus civitas perspiciatis.",
  "createdBy": {
    "name": "Robin Lesch"
  },
  "createdAt": {
    "name": "Thu Dec 07 2023 17:03:18 GMT+0000 (Coordinated Universal Time)"
  },
  "items": [
    {
      "id": 0,
      "productName": "Handmade Shoes",
      "productUuid": "8ae94b9f-2437-498c-9c08-ce6c886a41c3",
      "quantity": 6,
      "unitPrice": 507,
      "totalPrice": 507
    },
    {
      "id": 1,
      "productName": "Handcrafted Gloves",
      "productUuid": "4e6e1e15-b5ee-4578-a389-b2ece1d3e3ef",
      "quantity": 10,
      "unitPrice": 594,
      "totalPrice": 594
    }
  ],
  "archived": false,
  "success": true
}

Looks good, altho there is a problem... You may have noticed that the totalPrice is just a unitPrice.

5. Challenge

Can you go back to the Worker's code and fix it by calculating the actual total price for each item?

And for extra points (optional), can you also calculate and return the total price of the whole order?