⛅ Key-Value Storage With Cloudflare Workers KV (lesson 4)

Subscribe to my newsletter and never miss my upcoming articles

Serverless key-value storage or KV provides an easy way to store / retrieve data for your Cloudflare Workers. Best of all, Cloudflare has recently introduced a FREE tier for KV!

Cloudflare Workers Course Outline

  1. Getting Started with Serverless Cloudflare Workers
  2. Cloudflare Workers as a Web Server (with Webpack)
  3. Making API Calls From a Cloudflare Worker
  4. Key-Value Storage With Cloudflare Workers KV

About Workers KV

Workers KV reads respond incredibly fast. According to Cloudflare, as quickly as a cached static file would.

Workers KV is an eventually-consistent database. Changes may take up to 60 seconds to propagate (though in my experience it's much less). This means KV cannot be used in applications that require atomic transactions.

Workers KV is a key-value storage. Typical of KV storage solutions, there is no search. But there are list operations.

More on How KV Works

What We'll Be Making

For this lesson, we will be using Cloudflare KV to put a key, get a key, and list keys.

Starting Where We Left Off

In case you haven't been following along with the previous lessons (you don't have to), you can start here:

# continue where we left off
$ git clone https://github.com/joelnet/cloudflare-worker-website.git
$ cd cloudflare-worker-website
$ git checkout f6a224b37010d76d694320d4748ac07cb75f7369
$ npm ci

Create KV Namespace

Namespaces are a way to segregate KV stores. This is useful if you run many applications under Cloudflare or separation within an application. For example, you might want to create LOGINS and PROFILES Namespaces. You could also do this with a key prefix like logins:<login> or profiles:<profile>.

A Namespace can be created within the Cloudflare website or with the wrangler CLI.

I'm going to create a new Namespace called FILES.

$ wrangler kv:namespace create "FILES"

The output should look something like this. Be sure to paste that bottom piece into your wrangler.toml.

🌀  Creating namespace with title "my-website-FILES"
✨  Success!
Add the following to your configuration file:
kv_namespaces = [ 
         { binding = "FILES", id = "529b4c9be8b344f59adfe89cc9765879" }


Add an ESLint Global by opening .eslintrc.yml. This will prevent ESLint from complaining about the FILES global.

  FILES: readonly

The Setup

The plan is to create 3 files. One each to read, write, and list the keys. Then use KV to store markdown files and then display them as HTML.

First, setup the routing to the new files.

Open src/index.js and add the imports.

import filesPost from './pages/files.post'
import files from './pages/files'
import filesList from './pages/files.list'

Then add a new routes. /? is RegEx for optional /. .+ is RegEx for one or more character.

router.post('/files/?', () => filesPost(request))
router.get('/files/.+', () => files(request))
router.get('/files/?', () => filesList(request))

Adding a Key

Create src/pages/files.post.js. To create a key use the format: await NAMESPACE.put(key, value).

import { htmlResponse } from '../lib/responses'

const filePost = async request => {
    const { filename, contents } = await request.json()
    await FILES.put(filename, contents)
    return htmlResponse('SUCCESS')

export default filePost

Keys can auto-expire by setting the expiration. Expiring Keys

Reading a Key

Create src/pages/files.js. Reading a key uses the format: await NAMESPACE.get(key).

Note: There are more sophisticated ways to pull the filename off the URL, I'm using substring for the simplicity of this demo.

import marked from 'marked'
import { htmlResponse } from '../lib/responses'

import marked from 'marked'
import { htmlResponse } from '../lib/responses'

const files = async request => {
    const url = new URL(request.url)
    const id = url.pathname.substring(7)
    const file = await FILES.get(id)
    const html = marked(file)
    return htmlResponse(html)

export default files

Listing Keys

Create src/pages/files.list.js. The list uses the format: await NAMESPACE.list().

import { htmlResponse } from '../lib/responses'

const fileToLi = file => `
  <a href="/files/${file.name}">${file.name}</a>

const fileList = async () => {
    const files = await FILES.list()
    const lis = files.keys.map(fileToLi).join('')
    return htmlResponse(`<ul>${lis}</ul>`)

export default fileList

Lists also support paging and listing by prefix. Check the docs for more on lists.

Extra Credit

  • Using the same pattern, implement a delete with the method await NAMESPACE.delete(key).

  • Implement pagination on the list page to support > 1000 entries.

  • Experiment with Expiring Keys.

Refer to the API for reference.


We used Workers KV to create a file upload to upload markdown files as well as display those markdown files as HTML. As a bonus there is a page that lists the FILES uploaded.

Browse the repository at this point in history.

Subscribe to my Newsletter to continue learning about Cloudflare Workers!

Cheers 🍻

No Comments Yet