From Static Files to Fully Distributed Apps

Discussions about edge development tend to use the term "edge app" loosely. Often, what's actually running at the edge is a CDN caching static assets, while the core logic and data stay in a centralized region. But an application is only as fast as its slowest dependency. If your compute replicates to 300 locations while your database lives in a single region, every dynamic request still travels the long way around the globe.

A true edge stack requires both server-side compute and the datastore to be distributed. Cloudflare Workers provides the former as a serverless functions runtime, and Fauna's globally replicated document database covers the latter. With these two services, you can run a full application where logic, data reads, and data writes all execute in PoPs close to the user. This guide walks through building one such app: a URL shortener that runs entirely outside any centralized server.

Why Edge Deployment Is a Different Architecture

The conceptual shift from centralized servers is about physical location. Traditional hosting runs a finite set of servers, often in a single region or a handful of datacenters. For a user on another continent, every request must traverse an undersea cable or two, adding measurable latency. A distributed model places many lighter-weight instances across dozens or hundreds of locations. A user in Singapore hits a nearby point of presence, while a U.S.-based user hits another nearby one.

That model also improves resilience. And although maintaining that kind of infrastructure is prohibitive for individual developers, the major platform providers now abstract the deployment process entirely. On the low end, CDNs handle static content delivery. But CDNs can't execute business logic or interact with dynamic databases. That gap is what edge functions and edge databases fill.

Edge Functions

Edge functions are small, isolated code units that react to HTTP requests. The big three providers are Lambda@Edge, Deno Deploy, and Cloudflare Workers. For an edge app to remain fast, the code must fork close to the request origin. And critically, when that code queries a database, the database has to be equally accessible. A distributed database takes care of request routing, replication, and consistency across regions so you don't have to manage regional replica clusters yourself.

Building the URL Shortener

All the code for this example lives in a public repository (github.com/AsyncBanana/url-shortener). Clone it and change into the project directory to get started.

URL shorteners are the canonical edge workload: every redirect should be fast, hitting the user's nearest endpoint without a round trip to a home region. The frontend lives in public/; the server-side logic in src/. Aside from editing HTML, CSS, and client-side JavaScript in that public folder, the code you need to modify is confined to src/urlManager.js.

This is the URL manager

Configuration Steps

Begin with the tooling for Cloudflare Workers. You need the Wrangler CLI, available through npm:

npm install -g @cloudflare/wrangler
npm install

After that, sign up for a Workers account on the Cloudflare dashboard. Back in your terminal, run wrangler login, then wrangler whoami. Pull the account id from that output and drop it into wrangler.toml in the project root.

Setting up the Fauna Database

Next up is provisioning a database. Register for a Fauna tenant, then create a database with the classic region option. Choose a name like URL-Shortener, and leave demo data unchecked. With that resource in place, click Collections to create a new collection called URLs, then head over to the Security tab to generate an API key. The key goes into the project's .env file under the variable name FAUNA_KEY.

Writing the Query Logic

Fauna queries can be expressed through its query language, FQL, which surfaces as functions in a q namespace. Those functions are passed as arguments into FaunaClient.query().

In the createUrl function, you first need to insert the incoming URL as a document. The FQL expression for creating a record looks like this:

q.Create(q.Collection("urls"),{
  data: {
    url: url
  }
})

The generated document gets a reference ID which becomes the shortened handle. To return it, wrap q.Create inside a q.Select pass, keying on ["ref","id"], and make sure you're returning the awaited value of the Fauna query:

return await FaunaClient.query(
  q.Select(
    ["ref", "id"],
      q.Create(q.Collection("urls"), {
        data: {
          url: url,
        },
      })
    )
  );
}

Once you build the creation path, you can verify it: run wrangler dev, visit localhost:8787, paste a URL into the form, and a new worker domain or localhost route should appear. Right now that generated URL won't go anywhere; the request-then-redirect handler still needs its own query.

Look further down in that same module for the processUrl function. That is responsible for mapping the document id to the stored original URL. Run a Fauna query that reads the doc with the given id from the URLs collection:

const res = await FaunaClient.query(q.Get(q.Ref(q.Collection("urls"), id)));

Extract the target URL
from the result like this:

const res = await FaunaClient.query(q.Get(q.Ref(q.Collection("urls"), id)));
return res.data.url.url

That's the whole flow. With wrangler publish the application goes live on your workers.dev domain, and the entire loop—from request arrival to URL lookup and redirect—runs at the edge with no central dependency. Test it publicly to make sure everything is working as expected.

Areas for further work include adding response caching to eliminate even near-edge reads, tracking click analytics per short link, and in-depth reading of the Fauna or Workers documentation.