Building an OpenAPI compatible API for Cloudflare Workers in Go
In this post, I describe how I built a simple test API in Go and deployed it to Cloudflare Workers. I really like the Cloudflare Workers platform and was interested to understand how to do this. It’s worth noting that Go is currently not officially supported by Cloudflare, although the Workers do run WASM so it is possible to run Go code on the workers by compiling it to WASM. The test API developed here supports a few simple operations relating to managing results for running events.
When starting this, it was not at all clear which parts could be assembled to make this work - I learnt that I could use the following:
wrangler
- the Cloudflare tool for interacting with workers;wrangler
supports running the code locally in dev mode and also deploying it to Cloudflare’s platform. It also supports management operations such as database migrations and directly querying a database.wrangler
is based onnode
so you may require a functioningnode
installation;tinygo
- this was necessary to build to a small WASM compatible target which could be deployed to the Cloudflare platform;ogen
- a tool to generate Go code for the OpenAPI API definition;sqlc
- a tool to generate callable Go code from SQL database queries; it performs type validation for the database records and provides functions which make specific SQL queries;syumai/workers
- this is a Go project which supports deployment of Cloudflare workers and accessing functionality of the Cloudflare platform such as the database (D1) and the key-value store.
As can be seen, there are a few moving parts and how they fit together is not entirely obvious - hopefully the main points are made clear in this post.
The git repo containing the code which goes with this is here.
Running the API locally
To run the API locally, you will need to install wrangler
, tinygo
, ogen
and sqlc
. Installation of these is quite straightforward and documented in
the respective repos. It is assumed some other basic tooling is installed
including make
, curl
and jq
.
There are a couple of steps required to run the API locally. First,
build the workers-assets-gen
tool from the syumai/workers
repo.
<nixos>~/Work on โ๏ธ (eu-west-1)
๐ 14:06:17 โฏ git clone [email protected]:syumai/workers.git
Cloning into 'workers'...
remote: Enumerating objects: 2360, done.
remote: Counting objects: 100% (755/755), done.
remote: Compressing objects: 100% (370/370), done.
remote: Total 2360 (delta 406), reused 547 (delta 344), pack-reused 1605
Receiving objects: 100% (2360/2360), 383.67 KiB | 1.00 MiB/s, done.
Resolving deltas: 100% (1352/1352), done.
<nixos>~/Work on โ๏ธ (eu-west-1)
๐ 14:06:25 โฏ cd workers
<nixos>workers ๐ฃ main via ๐น v1.22.1 on โ๏ธ (eu-west-1)
๐ 14:06:33 โฏ go build ./cmd/workers-assets-gen
<nixos>workers ๐ฃ main [?] via ๐น v1.22.1 on โ๏ธ (eu-west-1)
๐ 14:06:41 โฏ ./workers-assets-gen --help
Usage of ./workers-assets-gen:
-mode string
build mode: tinygo or go (default "tinygo")
-o string
output dir path: defaults to "build" (default "build")
<nixos>workers ๐ฃ main [?] via ๐น v1.22.1 on โ๏ธ (eu-west-1)
๐ 14:06:47 โฏ
Next, clone the cf-race-api
repo.
<nixos>workers ๐ฃ main [?] via ๐น v1.22.1 on โ๏ธ (eu-west-1)
๐ 14:06:47 โฏ cd ..
<nixos>~/Work on โ๏ธ (eu-west-1)
๐ 14:07:59 โฏ git clone [email protected]:seanrmurphy/cf-race-api.git
Cloning into 'cf-race-api'...
remote: Enumerating objects: 39, done.
remote: Counting objects: 100% (39/39), done.
remote: Compressing objects: 100% (31/31), done.
remote: Total 39 (delta 10), reused 34 (delta 6), pack-reused 0
Receiving objects: 100% (39/39), 12.26 KiB | 12.26 MiB/s, done.
Resolving deltas: 100% (10/10), done.
<nixos>~/Work on โ๏ธ (eu-west-1)
๐ 14:08:03 โฏ
Generate the javascript files necessary to run the worker:
<nixos>~/Work on โ๏ธ (eu-west-1)
๐ 14:08:03 โฏ cd cf-race-api/
<nixos>cf-race-api ๐ฃ main via ๐น v1.22.1 on โ๏ธ (eu-west-1)
๐ 14:09:02 โฏ ../workers/workers-assets-gen
<nixos>cf-race-api ๐ฃ main via ๐น v1.22.1 on โ๏ธ (eu-west-1)
๐ 14:09:08 โฏ ls build
shim.mjs wasm_exec.js worker.mjs
<nixos>cf-race-api ๐ฃ main via ๐น v1.22.1 on โ๏ธ (eu-west-1)
๐ 14:09:13 โฏ
Note that a directory called build
has been created which contains these
files.
Ensure you’re logged in to Cloudflare using wrangler login
<nixos>cf-race-api ๐ฃ main via ๐น v1.22.1 on โ๏ธ (eu-west-1)
๐ 14:09:59 โฏ wrangler login
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
Attempting to login via OAuth...
Opening a link in your default browser: https://dash.cloudflare.com/oauth2/<REDACTED>
Successfully logged in.
Create a database for the application to use; in this case, we call the database cf-race-api
.
<nixos>cf-race-api ๐ฃ main via ๐น v1.22.1 on โ๏ธ (eu-west-1) took 14s
๐ 14:10:16 โฏ wrangler d1 create cf-race-api
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
โ
Successfully created DB 'cf-race-api' in region EEUR
Created your database using D1's new storage backend. The new storage backend is not yet recommended for production workloads, but backs up your data via
point-in-time restore.
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "cf-race-api"
database_id = "<REDACTED>"
Add the toml
output which specifies the database to your wrangler.toml
.
This associates this new database with this specific project. The wrangler
tool supports a number of database operations; for example, you can also list
your existing databases using wrangler d1 list
.
With the database created, it’s now possible to initialize it. By default, this
uses the content in the migrations
directory (although this can be changed by
adding a field to wranger.toml
) - in the git repo provided, the file
0000_create_race_info_and_results.sql
is used to specify which database
tables to create with which fields.
- perform the first db migration - this will just initialize a local copy of the database (which is completely separate from the Cloudflare hosted database)
<nixos>cf-race-api ๐ฃ main [!] via ๐น v1.22.1 on โ๏ธ (eu-west-1)
๐ 14:15:15 โฏ wrangler d1 migrations list cf-race-api --local
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
Migrations to be applied:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Name โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 0000_create_race_info_and_results.sql โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
<nixos>cf-race-api ๐ฃ main [!] via ๐น v1.22.1 via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:15:21 โฏ wrangler d1 migrations apply cf-race-api --local
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
Migrations to be applied:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ name โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 0000_create_race_info_and_results.sql โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ About to apply 1 migration(s)
Your database may not be available to serve requests during the migration, continue? โฆ yes
๐ Mapping SQL input into an array of statements
๐ Executing on local database cf-race-api (REDACTED) from .wrangler/state/v3/d1:
๐ To execute on your remote database, add a --remote flag to your wrangler command.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโ
โ name โ status โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโค
โ 0000_create_race_info_and_results.sql โ โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโ
<nixos>cf-race-api ๐ฃ main [!] via ๐น v1.22.1 via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:15:35 โฏ
With the database configured, you can now run the application locally:
<nixos>cf-race-api ๐ฃ main [!] via ๐น v1.22.1 via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:15:35 โฏ wrangler dev
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
Running custom build: make build
mkdir -p build
ogen ./swagger.yaml
INFO convenient Convenient errors are not available {"reason": "operation has no \"default\" response", "at": "swagger.yaml:20:9"}
sqlc generate
go mod tidy
tinygo build -o ./build/app.wasm -target wasm -no-debug
Your worker has access to the following bindings:
- D1 Databases:
- DB: cf-race-api (REDACTED)
[wrangler:inf] Ready on http://localhost:8787
โ Starting local server...
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ [b] open a browser, [d] open Devtools, [l] turn off local mode, [c] clear console, [x] to exit โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
You can then populate the database using scripts provided in the testing
directory.
<nixos>cf-race-api ๐ฃ main [!?] via ๐น v1.22.1 via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:19:07 โฏ cd testing
<nixos>cf-race-api/testing ๐ฃ main [!?] on โ๏ธ (eu-west-1)
๐ 14:19:10 โฏ set -x APIENDPOINT http://localhost:8787
<nixos>cf-race-api/testing ๐ฃ main [!?] on โ๏ธ (eu-west-1)
๐ 14:19:16 โฏ # if using bash, the above would be export APIENDPOINT=http://localhost:8787
<nixos>cf-race-api/testing ๐ฃ main [!?] on โ๏ธ (eu-west-1)
๐ 14:19:51 โฏ ./create-races.sh
Creating first race...
{"id":1,"location":"Dublin","created_at":"2024-05-05T14:20:01+02:00","name":"Dublin Marathon","event_date":"2024-05-01","run_types":[]}
Creating second race...
{"id":2,"location":"London","created_at":"2024-05-05T14:20:02+02:00","name":"London Marathon","event_date":"2024-05-10","run_types":[]}
Creating third race...
{"id":3,"location":"Zurich ","created_at":"2024-05-05T14:20:02+02:00","name":"Zurich Marathon","event_date":"2024-06-10","run_types":[]}โ
<nixos>cf-race-api/testing ๐ฃ main [!?] on โ๏ธ (eu-west-1)
๐ 14:20:02 โฏ cd utils
<nixos>cf-race-api/testing/utils ๐ฃ main [!?] on โ๏ธ (eu-west-1)
๐ 14:20:17 โฏ ls -l
total 8
-rwxr-xr-x 1 sean users 285 May 5 14:08 delete-all.sh
-rwxr-xr-x 1 sean users 391 May 5 14:08 query-db.sh
<nixos>cf-race-api/testing/utils ๐ฃ main [!?] on โ๏ธ (eu-west-1)
๐ 14:20:18 โฏ ./query-db.sh
Querying basic db info...
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
๐ Mapping SQL input into an array of statements
๐ Executing on local database cf-race-api (REDACTED) from ../../.wrangler/state/v3/d1:
๐ To execute on your remote database, add a --remote flag to your wrangler command.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
name โ sql โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
_cf_KV โ CREATE TABLE _cf_KV ( โ
key TEXT PRIMARY KEY,
value BLOB
) WITHOUT ROWID
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ d1_migrations โ CREATE TABLE d1_migrations( โ
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
sqlite_autoindex_d1_migratioโ null โ
s_1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
sqlite_sequence โ CREATE TABLE sqlite_sequence(name,seq) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ race_info โ CREATE TABLE race_info ( โ
id INTEGER PRIMARY KEY AUTOINCREMENT,
location TEXT NOT NULL,
name TEXT NOT NULL,
event_date INTEGER NOT NULL,
run_types TEXT NOT NULL,
created_at INTEGER NOT NULL
)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ race_results โ CREATE TABLE race_results ( โ
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
run_type TEXT NOT NULL,
race_id INTEGER NOT NULL,
start_time INTEGER NOT NULL,
end_time INTEGER NOT NULL,
FOREIGN KEY(race_id) REFERENCES race_info(id)
)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Querying race info table...
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
๐ Mapping SQL input into an array of statements
๐ Executing on local database cf-race-api (REDACTED) from ../../.wrangler/state/v3/d1:
๐ To execute on your remote database, add a --remote flag to your wrangler command.
โโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ
โ id โ location โ name โ event_date โ run_types โ created_at โ
โโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 1 โ Dublin โ Dublin Marathon โ 1714521600000000 โ ["marathon","half-marathon","10km"] โ 1714911601974000 โ
โโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 2 โ London โ London Marathon โ 1715299200000000 โ ["marathon"] โ 1714911602015000 โ
โโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 3 โ Zurich โ Zurich Marathon โ 1717977600000000 โ ["marathon","half-marathon"] โ 1714911602047000 โ
โโโโโโดโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโ
Querying race results table...
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
๐ Mapping SQL input into an array of statements
๐ Executing on local database cf-race-api (REDACTED) from ../../.wrangler/state/v3/d1:
๐ To execute on your remote database, add a --remote flag to your wrangler command.
In the above, you can see that the races have been added via the API and they are present in the database.
<nixos>cf-race-api/testing/utils ๐ฃ main [!?] via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:22:02 โฏ cd ..
<nixos>cf-race-api/testing ๐ฃ main [!?] on โ๏ธ (eu-west-1)
๐ 14:22:03 โฏ ./create-results.sh
Creating first race results...
Creating second race results...
Creating third race results...
<nixos>cf-race-api/testing ๐ฃ main [!?] on โ๏ธ (eu-west-1)
๐ 14:22:07 โฏ cd utils
<nixos>cf-race-api/testing/utils ๐ฃ main [!?] via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:23:55 โฏ ./query-db.sh
Querying basic db info...
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
๐ Mapping SQL input into an array of statements
๐ Executing on local database cf-race-api (REDACTED) from ../../.wrangler/state/v3/d1:
๐ To execute on your remote database, add a --remote flag to your wrangler command.
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
name โ sql โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
_cf_KV โ CREATE TABLE _cf_KV ( โ
key TEXT PRIMARY KEY,
value BLOB
) WITHOUT ROWID
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ d1_migrations โ CREATE TABLE d1_migrations( โ
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
sqlite_autoindex_d1_migratioโ null โ
s_1
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
sqlite_sequence โ CREATE TABLE sqlite_sequence(name,seq) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ race_info โ CREATE TABLE race_info ( โ
id INTEGER PRIMARY KEY AUTOINCREMENT,
location TEXT NOT NULL,
name TEXT NOT NULL,
event_date INTEGER NOT NULL,
run_types TEXT NOT NULL,
created_at INTEGER NOT NULL
)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ race_results โ CREATE TABLE race_results ( โ
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
run_type TEXT NOT NULL,
race_id INTEGER NOT NULL,
start_time INTEGER NOT NULL,
end_time INTEGER NOT NULL,
FOREIGN KEY(race_id) REFERENCES race_info(id)
)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โโโโ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Querying race info table...
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
๐ Mapping SQL input into an array of statements
๐ Executing on local database cf-race-api (REDACTED) from ../../.wrangler/state/v3/d1:
๐ To execute on your remote database, add a --remote flag to your wrangler command.
โโโโโโฌโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ
โ id โ location โ name โ event_date โ run_types โ created_at โ
โโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 1 โ Dublin โ Dublin Marathon โ 1714521600000000 โ ["marathon","half-marathon","10km"] โ 1714911601974000 โ
โโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 2 โ London โ London Marathon โ 1715299200000000 โ ["marathon"] โ 1714911602015000 โ
โโโโโโผโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 3 โ Zurich โ Zurich Marathon โ 1717977600000000 โ ["marathon","half-marathon"] โ 1714911602047000 โ
โโโโโโดโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโ
Querying race results table...
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
๐ Mapping SQL input into an array of statements
๐ Executing on local database cf-race-api (REDACTED) from ../../.wrangler/state/v3/d1:
๐ To execute on your remote database, add a --remote flag to your wrangler command.
โโโโโโฌโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโฌโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโ
โ id โ name โ run_type โ race_id โ start_time โ end_time โ
โโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 1 โ John Smith โ marathon โ 1 โ 1714554240000000 โ 1714565041000000 โ
โโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 2 โ Joanne Smith โ marathon โ 1 โ 1714554180000000 โ 1714564981000000 โ
โโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 3 โ Johannes Smith โ half marathon โ 1 โ 1714554120000000 โ 1714564921000000 โ
โโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 4 โ John Smith โ marathon โ 2 โ 1714554240000000 โ 1714565041000000 โ
โโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 5 โ Joanne Smith โ marathon โ 2 โ 1714554180000000 โ 1714564981000000 โ
โโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 6 โ Johannes Smith โ half marathon โ 2 โ 1714554120000000 โ 1714564921000000 โ
โโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 7 โ John Smith โ marathon โ 3 โ 1714554240000000 โ 1714565041000000 โ
โโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 8 โ Joanne Smith โ marathon โ 3 โ 1714554180000000 โ 1714564981000000 โ
โโโโโโผโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโผโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโค
โ 9 โ Johannes Smith โ half marathon โ 3 โ 1714554120000000 โ 1714564921000000 โ
โโโโโโดโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโดโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโ
<nixos>cf-race-api/testing/utils ๐ฃ main [!?] via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:23:58 โฏ
As can be seen in the above, the result records have been added to the database.
Running the API on Cloudflare
With the API working locally, running it on Cloudflare is very straightforward. As
with the local variant, it’s necessary to create the database and initialize the tables.
Once that is done, the application can be deployed using wrangler deploy
.
The --remote
flag is used to initialize the databases on Cloudflare’s platform.
<nixos>cf-race-api ๐ฃ main [!] via ๐น v1.22.1 via ๐ค v18.19.1 on โ๏ธ (eu-west-1) took 8m26s
๐ 14:25:24 โฏ wrangler d1 migrations list cf-race-api --remote
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
Migrations to be applied:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Name โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 0000_create_race_info_and_results.sql โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
<nixos>cf-race-api ๐ฃ main [!] via ๐น v1.22.1 via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:25:34 โฏ wrangler d1 migrations apply cf-race-api --remote
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
Migrations to be applied:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ name โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 0000_create_race_info_and_results.sql โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ About to apply 1 migration(s)
Your database may not be available to serve requests during the migration, continue? โฆ yes
๐ Mapping SQL input into an array of statements
๐ Parsing 3 statements
๐ Executing on remote database cf-race-api (<REDACTED>):
๐ To execute on your local development database, remove the --remote flag from your wrangler command.
๐ฃ Executed 3 commands in 0.5356000000000001ms
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโ
โ name โ status โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโค
โ 0000_create_race_info_and_results.sql โ โ
โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโ
<nixos>cf-race-api ๐ฃ main [!] via ๐น v1.22.1 via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:25:50 โฏ
To deploy:
<nixos>cf-race-api ๐ฃ main [!] via ๐น v1.22.1 via ๐ค v18.19.1 on โ๏ธ (eu-west-1)
๐ 14:25:50 โฏ wrangler deploy
โ
๏ธ wrangler 3.34.2 (update available 3.53.1)
-------------------------------------------------------
Running custom build: make build
mkdir -p build
ogen ./swagger.yaml
INFO convenient Convenient errors are not available {"reason": "operation has no \"default\" response", "at": "swagger.yaml:20:9"}
sqlc generate
go mod tidy
tinygo build -o ./build/app.wasm -target wasm -no-debug
Your worker has access to the following bindings:
- D1 Databases:
- DB: cf-race-api (REDACTED)
Total Upload: 1267.81 KiB / gzip: 425.62 KiB
Uploaded cf-race-api (3.69 sec)
Published cf-race-api (6.07 sec)
https://cf-race-api.<REDACTED>.workers.dev
Current Deployment ID: <REDACTED>
And then testing can be done as before, but the environment variable needs to be set to point at the Cloudflare’s platform rather than the local devleopment environment.
<nixos>cf-race-api/testing ๐ฃ main [!] on โ๏ธ (eu-west-1)
๐ 14:29:29 โฏ set -x APIENDPOINT https://cf-race-api.<REDACTED>.workers.dev
<nixos>cf-race-api/testing ๐ฃ main [!] on โ๏ธ (eu-west-1)
๐ 14:30:00 โฏ ./create-races.sh
Creating first race...
{"id":1,"location":"Dublin","created_at":"2024-05-05T12:30:08Z","name":"Dublin Marathon","event_date":"2024-05-01","run_types":[]}
Creating second race...
{"id":2,"location":"London","created_at":"2024-05-05T12:30:08Z","name":"London Marathon","event_date":"2024-05-10","run_types":[]}
Creating third race...
{"id":3,"location":"Zurich ","created_at":"2024-05-05T12:30:09Z","name":"Zurich Marathon","event_date":"2024-06-10","run_types":[]}โ
<nixos>cf-race-api/testing ๐ฃ main [!] on โ๏ธ (eu-west-1)
๐ 14:30:09 โฏ ./create-results.sh
Creating first race results...
Creating second race results...
Creating third race results...
<nixos>cf-race-api/testing ๐ฃ main [!] on โ๏ธ (eu-west-1)
๐ 14:30:28 โฏ ./query-races.sh
Querying first race...
{"id":1,"location":"Dublin","created_at":"2024-05-05T12:30:08Z","name":"Dublin Marathon","event_date":"2024-05-01","run_types":["marathon","half-marathon","10km"]}
Querying second race...
{"id":2,"location":"London","created_at":"2024-05-05T12:30:08Z","name":"London Marathon","event_date":"2024-05-10","run_types":["marathon"]}
Querying third race...
{"id":3,"location":"Zurich ","created_at":"2024-05-05T12:30:09Z","name":"Zurich Marathon","event_date":"2024-06-10","run_types":["marathon","half-marathon"]}โ
<nixos>cf-race-api/testing ๐ฃ main [!] on โ๏ธ (eu-west-1)
๐ 14:30:36 โฏ ./query-race-results.sh
Querying first race results...
[
{
"name": "John Smith",
"run_type": "marathon",
"start_time": "2024-05-01T09:04:00Z",
"end_time": "2024-05-01T12:04:01Z"
},
{
"name": "Joanne Smith",
"run_type": "marathon",
"start_time": "2024-05-01T09:03:00Z",
"end_time": "2024-05-01T12:03:01Z"
},
{
"name": "Johannes Smith",
"run_type": "half marathon",
"start_time": "2024-05-01T09:02:00Z",
"end_time": "2024-05-01T12:02:01Z"
}
]
Querying second race results...
[
{
"name": "John Smith",
"run_type": "marathon",
"start_time": "2024-05-01T09:04:00Z",
"end_time": "2024-05-01T12:04:01Z"
},
{
"name": "Joanne Smith",
"run_type": "marathon",
"start_time": "2024-05-01T09:03:00Z",
"end_time": "2024-05-01T12:03:01Z"
},
{
"name": "Johannes Smith",
"run_type": "half marathon",
"start_time": "2024-05-01T09:02:00Z",
"end_time": "2024-05-01T12:02:01Z"
}
]
Querying third race results...
[
{
"name": "John Smith",
"run_type": "marathon",
"start_time": "2024-05-01T09:04:00Z",
"end_time": "2024-05-01T12:04:01Z"
},
{
"name": "Joanne Smith",
"run_type": "marathon",
"start_time": "2024-05-01T09:03:00Z",
"end_time": "2024-05-01T12:03:01Z"
},
{
"name": "Johannes Smith",
"run_type": "half marathon",
"start_time": "2024-05-01T09:02:00Z",
"end_time": "2024-05-01T12:02:01Z"
}
]
<nixos>cf-race-api/testing ๐ฃ main [!] on โ๏ธ (eu-west-1)
๐ 14:30:47 โฏ
Comments
The Cloudflare tooling provides an excellent developer experience where running things locally is very similar to running things on their platform; creating and managing databases and key-value stores is done in exactly the same way.
The Cloudflare D1 database appears to be very sqlite
compatible and the Go
sqlc
tooling for sqlite
works well with D1. It may have been possible to
somehow merge the models generated by ogen
and sqlc
but I did not look into
this; this meant that there was a bit of code necessary to map between the
types created by ogen
and sqlc
.
ogen
OpenAPI tooling for Go provides an HTTP interface which can be easily
served via Cloudflare workers - it provides a standard HTTP server interface
which fits easily into the Cloudflare workers entry point.
Compile time with tinygo
can take maybe a minute (depending on compute
resources available) and it does not automatically recompile when changes are
made; this does impact the speed of developer iteration cycles and it’s likely
that using Javascript on the platform would support more rapid iterations.
tinygo
does result in small executables - in this work, the resulting
executable was about 1MB.
Latency on the Cloudflare workers platform was not negligible; I experienced latency of around 40ms even though the free tier only guarantees that workers with 10ms latency will not be terminated.
The restriction on executable size and latency do not apply for the paid tier;
perhaps it’s not necessary to use tinygo
if the paid tier is in use.
It’s also possible to specify other configuration aspects in the wrangler.toml
file, including, for example, a KV store and a rate limiter.
There are still quite a few cleanups to be done to the above code, but a solution to serving an OpenAPI service via Cloudflare Workers in Go is reasonably clear.
To make modifications to this project, eg to create your own functionality, the workflow is as follows:
- modify the OpenAPI definition in swagger.yaml;
- run ogen to generate the new Go code for the new API
- modify service.go to implement the new service
- run wrangler dev for testing the system locally
Closing remarks
Bringing up a basic API on Cloudflare workers developed in Go for test purposes is reasonably straightforward. In the next post, I’ll add some basic authentication mechanisms and a Key Value store.
Note: This post was also published on medium here.