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:
$ curl -u kay:fllstck https://auth.fllstck.workers.dev/api/breeds/image/random
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.