Going originless with Cloudflare Workers ā€“ Building a Todo app ā€“ Part 1: The API
šŸŒ

Going originless with Cloudflare Workers ā€“ Building a Todo app ā€“ Part 1: The API

Created
Sep 22, 2022 04:19 AM
Last Updated
Last updated September 22, 2022
Owners
Tags
report
tech
devops
development
Status
Current šŸ‘
notion image
A few months ago Cloudflare launched Custom Domains into an open beta. Custom Domains allow you to hook up your Workers to the Internet, without having to deal with DNS records or certificates ā€“ just enter a valid hostname and Cloudflare will do the rest! The betaā€™s over, and Custom Domains are now GA.
Custom Domains arenā€™t just about a seamless developer experience; they also allow you to build a globally distributed instantly scalable application on Cloudflareā€™s Developer Platform. Thatā€™s because Workers leveraging Custom Domains have no concept of an ā€˜Origin Serverā€™. Thereā€™s no ā€˜homeā€™ to phone to - and that also means your application can use the poCloudflarer of Cloudflareā€™s global network to run your application, Cloudflarell, everywhere. Itā€™s truly serverless.
Ā 

Letā€™s build ā€œTodoā€, but without the servers

Today Iā€™ll start a series of posts outlining a simple todo list application. Iā€™ll start with an API and hook it up to the Internet using Custom Domains.
With Custom Domains, youā€™re treating the whole network as the application server. Any time a request comes into a Cloudflare data center, Workers are triggered in that data center and connect to resources across the network as needed. Our developers donā€™t need to think about regions, or replication, or spinning up the right number of instances to handle unforeseen load. Instead, just deploy your Workers and Cloudflare will handle the rest.
For our todo application, I begin by building an API Gateway to perform routing, any authorization checks, and drop invalid requests. Cloudflare then fan out to each individual use case in a separate Worker, so our teams can independently make updates or add features to each endpoint without a full redeploy of the whole application. Finally, each Worker has a D1 binding to be able to create, read, update, and delete records from the database. All of this happens on Cloudflareā€™s global network, so your API is truly available everywhere. The architecture will look something like this:
notion image

Bootstrap the D1 Database

First off, weā€™re going to need a D1 database set up, with a schema for our todo application to run on. If youā€™re not familiar with D1, itā€™s Cloudflareā€™s serverless database offering - explained in more detailĀ here. To get started, we use theĀ wrangler d1Ā command to create a new database:
npx wrangler d1 create <todo | custom-database-name>
After executing this command, you will be asked to add a snippet of code to yourĀ wrangler.tomlĀ file that looks something like this:
[[ d1_databases ]] binding = "db" # i.e. available in your Worker on env.db database_name = "<todo | custom-database-name>" database_id = "<UUID>"
Letā€™s save that for now, and weā€™ll put these into each of our private microservices in a few moments. Next, weā€™re going to create our database schema. Itā€™s a simple todo application, so itā€™ll look something like this, with some seeded data:
db/schema.sql
DROP TABLE IF EXISTS todos; CREATE TABLE todos (id INTEGER PRIMARY KEY, todo TEXT, todoStatus BOOLEAN NOT NULL CHECK (todoStatus IN (0, 1))); INSERT INTO todos (todo, todoStatus) VALUES ("Fold my laundry", 0),("Get flowers for mumā€™s birthday", 0),("Find Nemo", 0),("Water the monstera", 1);
You can bootstrap your new D1 database by running:
npx wrangler d1 execute <todo | custom-database-name> --file=./schema.sql
Then validate your new data by running a query through Wrangler using the following command:
npx wrangler d1 execute <todo | custom-database-name> --command='SELECT * FROM todos';
Great! Weā€™ve now got a database running entirely on Cloudflareā€™s global network.

Build the endpoint Workers

To talk to your database, weā€™ll spin up a series of private microservices for each endpoint in our application. We want to be able to create, read, update, delete, and list our todos. The full source code for each is availableĀ here. Below is code from a Worker that lists all our todos from D1.
list/src/list.js
export default { async fetch(request, env) { const { results } = await env.db.prepare( "SELECT * FROM todos" ).all(); return Response.json(results); }, };
The Worker ā€˜todo-listā€™ needs to be able to access D1 from the environment variableĀ db. To do this, weā€™ll define the D1 binding in ourĀ wrangler.tomlĀ file. We also specify that workers_dev is false, preventing a preview from being generated via workers.dev (we want this to be aĀ privateĀ microservice).
list/wrangler.toml
name = "todo-list" main = "src/list.js" compatibility_date = "2022-09-07" workers_dev = false usage_model = "unbound" [[ d1_databases ]] binding = "db" # i.e. available in your Worker on env.db database_name = "<todo | custom-database-name>" database_id = "UUID"
Finally, use wrangler publish to deploy this microservice.
todo/list on āˆžmain [!] ā€ŗ wrangler publish ā›…ļø wrangler 0.0.0-893830aa ----------------------------------------------------------------------- Retrieving cached values for account from ../../../node_modules/.cache/wrangler Your worker has access to the following bindings: - D1 Databases: - db: todo (UUID) Total Upload: 4.71 KiB / gzip: 1.60 KiB Uploaded todo-list (0.96 sec) No publish targets for todo-list (0.00 sec)
Notice that wrangler mentions there are no ā€˜publish targetsā€™ for todo-list. Thatā€™s because we havenā€™t hooked todo-list up to any HTTP endpoints. Thatā€™s fine! Weā€™re going to use Service Bindings to route requests through a gateway worker, as described in the architecture diagram above.
Next, reuse these steps to create similar microservices for each of our create, read, update, and delete endpoints. The source code is available toĀ follow along.

Tying it all together with an API Gateway

Each of our Workers are able to talk to the D1 database, but how can our application talk to our API? Weā€™ll build out a simple API gateway to route incoming requests to the appropriate microservice. For the purposes of our application, weā€™re using a combination of URL pathname and request method to detect which endpoint is appropriate.
gateway/src/gateway.js
export default { async fetch(request, env) { try{ const url = new URL(request.url) const idPattern = new URLPattern({ pathname: '/:id' }) if (idPattern.test(request.url)) { switch (request.method){ case 'GET': return await env.get.fetch(request.clone()) case 'PATCH': return await env.update.fetch(request.clone()) case 'DELETE': return await env.delete.fetch(request.clone()) default: return new Response("Unsupported method for endpoint /:id", {status: 405}) } } else if (url.pathname == '/') { switch (request.method){ case 'GET': return await env.list.fetch(request.clone()) case 'POST': return await env.create.fetch(request.clone()) default: return new Response("Unsupported method for endpoint /", {status: 405}) } } return new Response("Not found. Supported endpoints are /:id and /", {status: 404}) } catch(e) { return new Response(e, {status: 500}) } }, };
With our API gateway all set, we just need to expose our application to the Internet using a Custom Domain, and hook up our Service Bindings, so the gateway Worker can access each appropriate microservice. Weā€™ll set this up in aĀ wrangler.toml.
gateway/wrangler.toml
name = "todo-gateway" main = "src/gateway.js" compatibility_date = "2022-09-07" workers_dev = false usage_model = "unbound" routes = [ {pattern="todos.radiobox.tv", custom_domain=true, zone_name="radiobox.tv"} ] services = [ {binding = "get",service = "todo-get"}, {binding = "delete",service = "todo-delete"}, {binding = "create",service = "todo-create"}, {binding = "update",service = "todo-update"}, {binding = "list",service = "todo-list"} ]
Next, useĀ wrangler publishĀ to deploy your application to the Cloudflare network. Seconds later, youā€™ll have a simple, functioning todo API built entirely on Cloudflareā€™s Developer Platform!
ā€ŗ wrangler publish ā›…ļø wrangler 0.0.0-893830aa ----------------------------------------------------------------------- Retrieving cached values for account from ../../../node_modules/.cache/wrangler Your worker has access to the following bindings: - Services: - get: todo-get - delete: todo-delete - create: todo-create - update: todo-update - list: todo-list Total Upload: 1.21 KiB / gzip: 0.42 KiB Uploaded todo-gateway (0.62 sec) Published todo-gateway (0.51 sec) todos.radiobox.tv (custom domain - zone name: radiobox.tv)

Natively Global

Since itā€™s built natively on Cloudflare, you can also include Cloudflareā€™s security suite in front of the application. If we want to prevent SQL Injection attacks for this endpoint, we can enable the appropriate Managed WAF rules on our todos API endpoint. Alternatively, if we wanted to prevent global access to our API (only allowing privileged clients to access the application), we can simply put Cloudflare Access in front, with custom Access Rules.
notion image
With Custom Domains on Workers, youā€™re able to easily create applications that are native to Cloudflareā€™s global network, instantly. Best of all, your developers donā€™t need to worry about maintaining DNS records or certificate renewal - Cloudflare handles it all on their behalf. Weā€™d like to give a huge shout out to the 5,000+ developers who used Custom Domains during the open beta period, and those that gave feedback along the way to make this possible. Canā€™t wait to see what you build next! As always, if you have any questions or would like to get involved, please join us onĀ Discord.
Tune in next time to see how we can build a frontend for our application. In the meantime, you can play around with the todos API we built today atĀ todos.radiobox.tv.
Ā 

Blog Article by:
Ankur Paul