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
- Getting Started with Serverless Cloudflare Workers
- Cloudflare Workers as a Web Server (with Webpack)
- Making API Calls From a Cloudflare Worker
- Key-Value Storage With Cloudflare Workers KV
- [Bonus] Smart Routing with Cloudflare Workers
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.
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" }
]
ESLint
Add an ESLint Global by opening .eslintrc.yml
. This will prevent ESLint from complaining about the FILES
global.
globals:
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 => `
<li>
<a href="/files/${file.name}">${file.name}</a>
</li>
`
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.
Summary
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 🍻
- Join my 📰 Newsletter
- Subscribe to my 📺 YouTube, JoelCodes
- Say hi to me on Twitter @joelnet