Serverless Redis with Cloudflare Workers & Pulumi
Tags: redis, serverless, backend, database
Published: 2021-06-16 15:32:01

---
title: Serverless Redis with Cloudflare Workers & Pulumi
published: true
tags: redis, serverless, backend, database
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ue9dztf9ssf4390f1jy.jpg
---

[Cloudflare Workers](https://workers.cloudflare.com/) are an exciting function as a service offering. They are built on the Service Worker web standard and are globally deployed on Cloudflare's edge network. This edge deployment makes them a low latency solution, so it comes naturally to use them with a low-latency database like Redis. 

The problem is, Cloudflare Workers only allow requests to HTTP and HTTPS ports, so connecting to a Redis instance with a regular client isn't possible.

Luckily [Upstash](https://upstash.com?utm_source=kay_cf), a serverless Redis hosting provider, has now added a REST API to their product. This way, we can profit from the low latency of Redis inside a Cloudflare Worker.

# Example Project

I created an example project with [Pulumi](https://www.pulumi.com/), infrastructure as code tool like the AWS CDK, but it works with non-AWS cloud providers, like Cloudflare, too!

You can find the complete code [on GitHub](https://github.com/kay-is/pulumi-cloudflare-upstash).

It creates a Worker script and links it up with a Worker route. This way, the script can be accessed from a custom domain.

## Prerequisites

- [Node.js & NPM installation](https://nodejs.org/en/)
- [Pulumi installation](https://www.pulumi.com/docs/get-started/install/) and [Pulumi account](https://app.pulumi.com/signin)
- [Cloudflare account](https://dash.cloudflare.com/sign-up/)
- [Upstash account](https://console.upstash.com/login?utm_source=kay_cf)

## Setup

When you clone the project from GitHub, you have to login into your Pulumi account and provide the project with Cloudflare and Upstash credentials.

### Cloudflare Credentials

First, we need to gather the Cloudflare credentials.

The API token can be created in the [Cloudflare Dashboard](https://dash.cloudflare.com/profile/api-tokens).

Click on "Create Token" and then on "Edit Cloudflare Workers." This way, the token only has permissions to mess with Workers and not other Cloudflare resources.

If you click through that wizard, you will be presented with the token at the end, copy it, and use the following Pulumi CLI command inside the project directory:

    $ pulumi set config cloudflare:apiToken <YOUR_API_TOKEN> --secret

Next, you need to get your Cloudflare account ID. You can find it in the Cloudflare Dashboard when you click on your domain. Scroll down, and on the right side is the account ID together with the zone ID, which we will need next.

    $ pulumi set config cloudflare:accountId <YOUR_ACCOUNT_ID>

Then we need the zone ID from Cloudflare; otherwise, we can't use a custom domain for the Worker; it's right above the account ID we copied in the last step.

Open the `index.ts` file and replace `<CLOUDFLARE_ZONE_ID>` with your zone id.

```typescript
new cloudflare.WorkerRoute("blog-route", {
  zoneId: "<CLOUDFLARE_ZONE_ID>",
  pattern: "example.com/posts",
  scriptName: blogScript.name,
});
```

You also have to replace the domain in the `pattern` with your domain; example.com won't work.

### Upstash Credentials

Next, we need the Upstash credentials; otherwise, our Cloudflare Workers can't talk to the database.

First, we have to create a new database in [the Upstash console](https://console.upstash.com/pages/overall).

Click on "Create New Database," give it a name, and choose a region. Then you can click on the freshly created database to get the credentials for the REST API.

There is a REST API button that displays an example request with cURL. 

    curl <YOUR_DB_URL>/set/foo/bar \
      -H "Authorization: Bearer <YOUR_DB_TOKEN>"

We need two parts here. The API token and the URL of the database.

The URL is everything between `curl` and `/set/foo/bar`, and the token is everything after `Bearer` (without the last quotation mark).

These two strings need to be added to the `index.ts`; this time, we need to replace the config for the `WorkerScript`.

```typescript
const blogScript = new cloudflare.WorkerScript("blog-script", {
  name: "blog-script",
  content: workerCode,
  plainTextBindings: [{ name: "DB_URL", text: "<UPSTASH_DB_URL>" }],
  secretTextBindings: [{ name: "DB_TOKEN", text: "<UPSTASH_DB_TOKEN>" }],
});
```

The bindings we define make new global variables available inside the worker script. Namely `DB_URL` and `DB_TOKEN`.

## Deployment

To deploy the whole thing, we have to run `pulumi up`. Pulumi will take care of provisioning the Cloudflare resources for us.

    $ pulumi up

## Usage

If everything went correctly, you should have a `/posts` endpoint on your domain that accepts `GET`, `POST`, and `DELETE` requests.

    http://example.com/posts

# How to Call Redis with REST?

Upstash has [excellent documentation](https://docs.upstash.com/features/restapi?utm_source=kay_cf) for their new REST API feature if you want to dive deeper, but let's look at some examples.

The Redis REST API is called like this:

    GET https://us1-merry-cat-32748.upstash.io/redis-command/arg1/arg2/...?_token=...

To call the API directly in JavaScript could look like this:

```javascript
const response = await fetch("https://us1-merry-cat-32748.upstash.io/incr/post:id?_token=...");
const { result } = await response.json();
const newPostId = result;
```
First, we send a request with `fetch` to the correct database endpoint. It includes the Redis command, the args, and the token.

Then we convert the response to a JavaScript object and extract the `result` property from it.

In the `worker.js` of the example project, I wrote a straightforward client for the REST API.

```javascript
async function callRestApi(command, ...args) {
  args = Array.isArray(args[0]) ? args[0] : args;
  const response = await fetch(
    `${DB_URL}/${command}/${args.join("/")}?_token=${DB_TOKEN}`
  );
  const data = await response.json();
  return data.result;
}
```

The client uses the two global variables, the `DB_URL` and the `DB_TOKEN`, which we defined as bindings for our Worker script and a Redis command plus its arguments.

This utility function simplifies the calls to the database a bit. The example call from above would now look like this:

    const newPostId = await callRestApi("incr", "post:id");

The API client can also be used with an array to execute a command with a dynamic amount of arguments.

A `GET` request to our Cloudflare Worker would execute the following code:

```javascript
async function listPosts() {
  const postKeys = await callRestApi("smembers", "posts");
  const posts = await callRestApi("mget", postKeys);
  return new Response(
    JSON.stringify(posts.map((text, i) => ({ id: postKeys[i], text })))
  );
}
```

First, it gets all `postKeys` from a key called `posts`. The command is `smembers`, because the `posts` key is a Redis Set.

Then it gets all the posts with `mget`, the multi-get command of Redis, that accepts multiple keys as arguments.

In this example, the posts are just strings, not a complex object, so in the end, we use `map` to relate every text to its key.

# Conclusion

Cloudflare Workers is a low-latency FaaS product, so using a low-latency database is the obvious choice here.

The port restrictions for Cloudflare Workers rule out some database choices, but Upstash gets around with its new REST API. It's as simple as you expect a Redis API to be, so not much new is to learn. 

If you want to learn what Upstash brings that Workers KV doesn't, there is also [a new article](https://blog.upstash.com/redis-cloudflare-workers?utm_source=kay_cf) explaining all this.