loading...
Cover image for Cloudflare Workers KV Introduction
Fullstack Frontend

Cloudflare Workers KV Introduction

kayis profile image K ・6 min read

Cloudflare Workers (2 Part Series)

1) Cloudflare Workers Introduction 2) Cloudflare Workers KV Introduction

In my previous article, I explained Cloudflare Workers (CFW) and showed you a basic example. In this article, I will talk about Cloudflare Workers KV (KV), the serverless key-value store from Cloudflare.

What is Cloudflare Workers KV?

KV is a serverless distributed eventually consistent key-value storage solution released in May 2019. It's deployed on the edge of the Cloudflare network like CFW, so the data you put into it is close to the CFW that uses it and, in turn, close to the client that sent a request to that CFW.

The eventual consistency means that it can take up to 60 seconds for a write to be propagated through the whole network. Also, every key inside the KV allows for one write per second. So KV is mainly suited for data that isn't written often.

KV can be accessed via an API, the Wrangler CLI, and directly from a CFW. This enables external services to update KV data so it can be read by a CFW without a redeploy.

The maximum size of a key is 512 Bytes, and the maximum size of a value is 10 MB. The values can be of type String, ReadableStream, and ArrayBuffer, so we can also store binary data!

Every account can have a maximum of 100 namespaces, and every namespace can have an unlimited amount of keys.

Important note: KV isn't free.

You have to get a subscription to the Workers Unlimited plan, which costs $5 a month. The plan includes 1GB of storage, 10 million reads, and 1 million writes, 1 million updates, and 1 million deletes per month.

If you go over these limits the costs are:

  • $0.50 for 1GB
  • $0.50 for 10 million reads
  • $5 for 1 million writes
  • $5 for 1 million updates
  • $5 for 1 million deletes

What are good Use Cases?

The deployment of a CFW is quite fast, so if you have data that is written much more often than it's read, you can simply put the data into the code and be done with it. But a CFW's maximum script size is 1 MB; for some use cases, this is simply not enough.

Authentication

Authentication is one way to use KV because the CFW docs say: "All values are encrypted at rest with 256-bit AES-GCM, and only decrypted by the process executing your Worker scripts or responding to your API requests."

If you put your API behind a CFW, you can implement authentication right inside the CFW and keep it out of your API entirely, simplifying its code.

Configuration

If you build a highly configurable system with CFW, that config data can also live in KV. This enables you to update it externally, without any CFW redeploys.

Website Files

If you host a website, chances are that you have a large number of small files, HTML, CSS, JavaScript, images, you name it. With 10 MB maximum file size in KV, most website files should neatly fit into that limit.

With the rise of the JAMStack, it's nice to know that with KV, there is a way to deploy the static files right at the edge, so they are close to the user. After all, low-latency is what it's all about!

Cloudflare even has optimized its static website deployment workflow with Cloudflare Workers Sites.

Example: Creating an Authorization Worker

Now that we know what KV is let's build a CFW that uses it!

In this example, we will build a simple authorizer that can be put in front of an API to check if a user is allowed to access that respective API.

Pre-Requisites

For this tutorial, you need Node.js v12 and a Cloudflare account with a "Workers Unlimited" subscription that costs $5 per month.

Installing the CLI

Developing and deploying CFW is done with the help of a CLI called Wrangler. We will install it via NPM.

$ npm i @cloudflare/wrangler -g

Initializing a Project

The CLI can then be used to create a new CFW project.

$ wrangler generate auth
$ cd auth
$ npm i

Creating a KV Namespace

A KV namespace is used to separate data. We can create one with the following Wrangler CLI command:

$ wrangler kv:namespace create "ACCOUNTS"

This will output a namespace ID which we have to include the new namespace into our wrangler.toml file.

kv-namespaces = [
    {binding = "ACCOUNTS", id = "<NAMESPACE_ID>"}
]

After this, we get access to a global ACCOUNTS object inside our CFW script.

Putting a Password Hash into KV

To get passwords into our freshly created ACCOUNTS namespace in KV, we can also use the Wrangler CLI.

The following command will add a key "kay" with the SHA-256 hash for the password "fllstck" as value.

$ wrangler kv:key put --binding=ACCOUNTS \
"kay" \
"69953BBB597B77EDB5FA691FD6396D69756EB949A87F250E32F799AB59F796BD"

We will be able to access the value inside of the CFW with this code:

const passwordHash = await ACCOUNTS.get("kay");

Implementing the Worker Script

The authorizer is a CFW in front of an API. This requires the API domain to be handled by Cloudflare DNS.

Put the following JavaScript code into your index.js:

const API_HOST = "https://dog.ceo";

addEventListener("fetch", (event) => {
  event.respondWith(authorize(event.request));
});

async function authorize(request) {
  const [type, token] = request.headers.get("Authorization").split(" ");

  if (type !== "Basic") {
    return new Response("", {
      status: 400,
      statusText: `Unsupported authentication type "${type}"`,
    });
  }

  const [username, password] = atob(token).split(":");

  const accountHash = await ACCOUNTS.get(username);
  const suppliedHash = await hash(password);

  if (suppliedHash !== accountHash) {
    return new Response("", {
      status: 401,
      statusText: "Unauthorized.",
    });
  }

  if (ENVIRONMENT === "staging") {
    const url = new URL(request.url);
    request = new Request(API_HOST + url.pathname, request);
  }

  return fetch(request);
}

async function hash(password) {
  const msgUint8 = new TextEncoder().encode(password);
  const hashBuffer = await crypto.subtle.digest("SHA-256", msgUint8);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}

Let's go through it!

The example uses basic authorization, so we have to extract the Authorization header as a first step.

The Authorization header is a string that includes an authorization type separated with a space from a token.

In our case, the type should be "Basic".

The token consists of a username and a password separated by a colon and encoded to Base64.

In our KV namespace ACCOUNTS, we store password hashes keyed by usernames, so we can use the username to find a corresponding password hash.

After we searched for a hash, we will hash the password sent by the client with the hash() helper function and compare it with the stored password hash in KV.

If no hash was found in KV or the hashes don't match up, we respond with a 401 status.

If everything matches up, we will send the request to the origin server, which in this case would be our actual API, and redirect the APIs response to the client.

Note: In this example, the host part of the request URL is overwritten on a staging deployment because the worker will be hosted on workers.dev and not your own domain. Also, for this example, we use the public dog.ceo API.

Deploying the Worker Script

We use the Wrangler CLI again to deploy the script. For this, we have to add our account ID to the wrangler.toml file, it can be found on your Cloudflare dashboard under the menu point Workers.

After we added the ID, we can do a non-production deploy with the following command:

$ wrangler publish

Using the Worker

If everything went good, we can access our worker at:

https://auth.<ACCOUNT_NAME>.workers.dev/api/breeds/image/random

Our CFW will reuse the path of that URL, but replace the host part of it. We also have to include the Authorization header with basic auth credentials.

An example cURL request could look like this:




Summary

KV is a pretty powerful addition to Cloudflare Workers that makes them useful for even more use cases.

And the best of it: it isn't much harder to use than the Web Storage API and KV can be accessed via the Wrangler CLI and the Cloudflare API. API access makes them very powerful.

But it's still important to keep the limits in mind!

A maximum of 10 MB per value and up to 60 seconds to populate a write can be a dealbreaker for some types of apps.

Cloudflare Workers (2 Part Series)

1) Cloudflare Workers Introduction 2) Cloudflare Workers KV Introduction

Posted on Jun 11 by:

kayis profile

K

@kayis

Taking care of developer relations at Moesif and creating educational content at fllstck.dev

Discussion

markdown guide
 

I wasn't aware Cloudflare had key-value storage!

Looks very interesting indeed. Although pricing is a bit high, if you compare it with an API Gateway and DynamoDB on AWS, it's not much more expensive. Workers are a lot cheaper than an AWS API Gateway + Lambda, so the bottom line is more persuasive for Cloudflare.

Good post Kay, thanks for sharing!

 

Glad you like it!

I was surprised too. This won't replace a database for all use cases, but it certainly pushes what you can do before you need one :)

 

Yes, definitely. It really opens up interesting use cases.

It could be used to support Tensorflow JS, for example, in order to have AI models running with very low latency in the browser backed by data stored in KV.

When data needs to be persisted long term, Workers can send the data to an external DB as well but asynchronously...

Haha, could be tight.

TFJS ES2017 is 818KB and CFW allow 1 MB max :D

Edit: I also read TFJS uses WebGL, which might not be available in CFW. I saw they support a WebAssembly alternative, but I don't know if that will get you over 1 MB.

Sure, yes, I mean using TFJS in the browser, backed by models stored in CF KV.

Wasn't aware of the 1 MB payload limit in Workers. What's the point if KV can store up to 10 MB? At first glance, I would presume Workers should be able to support at least the same limits as KV...

But anyway, models could be stored statically in the CDN anyway

Oh, I didn't mean payload. I meant script size.

Don't know really about the payload.