In the early stages of building any application, monolithic architectures can seem like the simplest solution. Everything contained in one code base. But as your application begins to scale, the monolithic approach starts to lead to more problems than it solves. Changes become riskier, deployments slower, and maintenance can turn into a nightmare as new features and fixes can inadvertently break existing functionality. In many instances, developers turn to a microservice architecture where managing a system of smaller, independent services, that work together takes away many risks and provides a lot of upside.
Enter Cloudflare Workers – a powerful serverless platform that not only simplifies creating and managing microservices but also allows them to be seamlessly bound together. In this blog, we’ll explore how you can leverage Cloudflare Workers to build a modern microservice architecture, complete with edge computing benefits and low-latency communication.
Above we show an example of what a commerce platform microservice architecture might look like. You have a "Core Service” where maybe some of the routing functionality exists and calls out to the number of other services that exist for them to handle their specialized functionality.
When a developer or team is making adjustments to a service such as User Auth, that team can feel more confidence in their own deployment cycle when it is more standalone and does not involve the entire application stack. So let’s go ahead and dive in.
Microservices in StarbaseDB
Before we dive into building our own microservice architecture with Cloudflare Workers I wanted to take a moment to touch on both why and how we use it in StarbaseDB – to lead by example.
For those who don’t know, StarbaseDB provides a lot of functionality that automatically supercharges your database. Out of the box you get a SQLite database, an interface to view your database, REST API, web socket communication, data importing & exporting, and much more. The goal of StarbaseDB is to eliminate as many of the development challenges that exist near the database for developers – and sometimes that means tackling problems that affect some people, but not all.
That’s where offering a variety of smaller services as optional add-ons helps showcase how great this service based architecture can be. An example service for StarbaseDB is user authentication and providing a solution to allow users to sign up, login and have a session without you needing to write a single line of code. Why should we offer a service to do this out of the box instead of letting teams write it themselves? They can still do that of course! But because user sessions typically need authorized before continuing to query a database (and all of the session information likely lives in the database anyways) there is an opportunity for us to help gate keep that access to the database for those looking to get jumpstarted quickly.
We’re big believers that micro services help teams stay lean, iterate quickly, maintain focus, and ship fast.
See examples of our services in the Pull Requests section at the bottom of this blog.
Project Setup
For this to work we need to create 2 separate Workers. One will be our main component, while the second will serve as our sample service. In this example our main service will serve as a router mechanism that listens for incoming routes and then takes traffic to another service to handle feature-specific logic.
Roll up your sleeves, open up your favorite IDE and let’s get started.
Create Main Component
We first need to create two folders at the root of our project that contains the code for each of the services we are going to program. Go ahead and create the following two folders at the root level of your project:
core
- this will handle our incoming request routingauth
- this will be specifically for all auth functionality
Traditionally you’d likely use other tools to serve as an incoming request routing mechanism but without introducing too many concepts or unnecessary code I want to keep this simple so you can see how easy it truly is.
With our two folders created, go ahead and create the following three files inside of our core
folder:
./core/wrangler.toml
./core/package.json
./core/index.ts
Let’s take a look at what each file should contain as code.
wrangler.toml
name = "microservice_core"
main = "./index.ts"
compatibility_date = "2024-09-25"
[observability]
enabled = true
If you’re not familiar with Cloudflare’s Wrangler, the above file is used as metadata for our deployment to properly name or identify our service, and turn on some additional features (such as logging here). It also points to our main entry point file.
package.json
{
"name": "microservice-core",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240925.0",
"typescript": "^5.5.2",
"wrangler": "^3.60.3"
}
}
Above is a template package.json file I use for starting with Cloudflare Workers. It provides the scripts to deploy our code with the proper dependencies.
index.ts
interface Env {
}
const jsonResponse = (data: unknown, status = 200): Response => {
return new Response(JSON.stringify(data), {
status,
headers: {
'Content-Type': 'application/json',
},
});
};
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const { pathname } = url;
const method = request.method.toUpperCase();
if (pathname === '/api/hello' && method === 'GET') {
return jsonResponse({ message: 'Hello, World!' });
}
return jsonResponse({ error: 'Not Found' }, 404);
}
};
Our index file above is a pretty lightweight Cloudflare Worker. The default export has a fetch(..)
function available to us that listens to web traffic that reaches the destination of this Worker and allows us to implement custom logic to do what we want with those requests. In this instance we’re just supplying a test /api/hello
route to make sure our Worker is accessible when we deploy it.
Create Service Component
Our second service component here is going to look slightly different when it comes to the code piece, but not by much. Now in our second root folder of auth
we’ll create a similar file structure as we did above.
./auth/wrangler.toml
./auth/package.json
./auth/index.ts
Again, let’s look at what the code for each file looks like.
wrangler.toml
name = "microservice_auth"
main = "./index.ts"
compatibility_date = "2024-09-25"
[observability]
enabled = true
package.json
{
"name": "microservice-auth",
"version": "0.0.0",
"private": true,
"scripts": {
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240925.0",
"typescript": "^5.5.2",
"wrangler": "^3.60.3"
}
}
index.ts
import { WorkerEntrypoint } from "cloudflare:workers";
const jsonResponse = (data: unknown, status = 200): Response => {
return new Response(JSON.stringify(data), {
status,
headers: {
'Content-Type': 'application/json',
},
});
};
export default class AuthEntrypoint extends WorkerEntrypoint {
async fetch() { return new Response(null, {status: 404}); }
async handleAuthRequest(url: string, method: string) {
if (url === '/api/login' && method === 'GET') {
return jsonResponse({ message: 'You reached the auth microservice!' });
}
return new Response(null, {status: 404});
}
}
The first two files are nearly identical with just name changes, but the last file has a default class that extends WorkerEntrypoint
. What the WorkerEntrypoint
class allows is for our core
service to be able to identify this deployment by its class name which we cover in the next step. But similarly to our first class we can start writing whatever application level logic we want and return any response back to the user.
Binding Services in Workers
If we haven’t already let’s go ahead and deploy both of our services to Cloudflare. Inside each of our two root folders, core
and auth
, we can run a command line from our Terminal to npm i && npm run cf-typegen && npm run deploy
which should walk you through deploying to your connected Cloudflare account.
With both of our services now deployed we need to find a way to connect them. As it stands they are just two isolated, independent services unknowing that the other even exists.
Add Service to Wrangler
Going back to our core
service we need to tell it that there is another service it can begin to reference and know about. Open up the ./core/wrangler.toml
file and add the following lines of code to the end of the file.
[[services]]
binding = "AUTH" # How we want to reference it
service = "microservice_auth" # Name from our services wrangler
entrypoint = "AuthEntrypoint" # Class name
What this tells our Worker is that it should be able to see another Worker called microservice_auth
in the same Cloudflare account, and now it knows both what to call it internally, AUTH
, and how to hit the entry point of that file – our AuthEntrypoint
class.
Update our Env
With the wrangler file updated, you can now execute the following command again to update our generated file worker-configuration.d.ts
that shows we have access to those types now.
npm run cf-typegen
Now in our ./core/index.ts
file we can update the Env interface previously emptily declared at the top of our file so now the rest of our file knows how to call properly into this new entry point.
interface Env {
AUTH: {
handleAuthRequest(url: string, method: string): Promise<any>;
}
}
Direct Traffic to Service
You might be happy to hear that everything is now in place. Now, all we need to do is direct traffic to our auth service when we see a user hitting our Worker URL. We already handled in our ./core/index.ts
file when a user would hit our Worker with the URL https://microservice_core.YOUR-IDENTIFIER.workers.dev/api/hello
the browser would print out a JSON message of “Hello, World!”.
Now we want to allow users to also access the following URL path and let our auth service handle how it should respond.
if (pathname === '/api/hello' && method === 'GET') {
return jsonResponse({ message: 'Hello, World!' });
} else if (pathname.startsWith('/api/auth')) {
return await env.AUTH.handleAuthRequest(pathname, request.method);
}
After we make these changes all we need is one final deployment of our core service! So let’s go ahead and run the following command inside our ./core
folder:
npm run cf-typegen && npm run deploy
Now you can visit the following URL’s on your browser and see both services in action. You can get your URL from the message output of your npm run deploy
command to replace with what I have below, but the paths should remain the same at the end.
Core: https://microservice_core.YOUR-IDENTIFIER.workers.dev/api/hello
Auth: https://microservice_core.YOUR-IDENTIFIER.workers.dev/api/auth/login
Conclusion
There is something absolutely wild about being able to deploy a custom backend to the internet in 3 files and with 1 command. It allows us to create each piece of functionality that should be a siloed process, as its own deployment. As we have shown, it can be done without headaches, planning or preparation – you can just do it.
If you anticipate your project growing to any size where greater than a handful of engineers will end up becoming involved I would highly encourage you to try starting your next project with a microservice architecture early. You’ll reap the benefits later when you are not playing whack-a-mole with bugs as multiple engineers are touching lines of code they think but are not certain should only impact their code and nobody else.
Pull Requests
Want to see the code that was contributed alongside this article? Check the contributions out below!
https://github.com/Brayden/starbasedb/pull/26 (User Authentication)
https://github.com/Brayden/starbasedb/pull/27 (Dynamic Data Masking)
Join the Adventure
We’re working on building an open-source database offering with building blocks we talk about above and more. Our goal is to help make database interactions easier, faster, and more accessible to every developer on the planet. Follow us, star us, and check out Outerbase below!
Twitter: https://twitter.com/BraydenWilmoth
Github Repo: github.com/Brayden/starbasedb
Outerbase: https://outerbase.com