Birdor Links architecture: workspace-first SaaS edges on Cloudflare

How we structure a Cloudflare-native short URL service with explicit HTTP boundaries, workspace-scoped authorization, and 34-table D1 schema that separates edge redirects from background side effects.

Architecture · Sep 25, 2026 · 5 min read

BY / Birdor Engineering · Platform Architecture

Verified Sep 25, 2026

On this page · 9 sections

Problem

Short URL services accumulate four layers of complexity quickly: link ownership, workspace isolation, analytics collection, and edge redirect performance. When these concerns mix inside a single request handler, a redirect response blocks on database writes, analytics events become best-effort, and workspace boundaries leak through shared state.

Birdor Links is a workspace-first SaaS built on Cloudflare Workers. Its design constraint is simple: a redirect at /r/:slug must remain fast and fail-closed, while workspace management, link analytics, and background jobs can be slower and eventually consistent.

Summary

The architecture separates three concerns explicitly:

  • Edge redirect path — KV lookup + D1 policy check, non-blocking, sub-50ms target
  • Workspace management API — Hono routes with Better Auth session enforcement, Drizzle ORM on D1
  • Background side effects — Analytics Engine writes, CSV import/export via Queues + R2, webhook delivery via Durable Objects

A 34-table D1 schema enforces workspace scoping at the database level, and every write route validates both session freshness and workspace membership before reaching the repository layer.

Architecture

PLATE / MERMAID REFERENCE
flowchart LR
  Client[Client] --> Worker[Hono Worker]
  Worker --> Auth[Better Auth Session]
  Worker --> Redirect[/r/:slug]
  Worker --> API[/api/v1/*]
  Redirect --> KV[(KV Cache)]
  Redirect --> D1Policy[D1 Policy Read]
  API --> D1[(D1 - 34 tables)]
  API --> AE[(Analytics Engine)]
  API --> Queue[Queue / R2 / DO]

Edge redirect path

The redirect route is the hottest path and the most constrained:

GET /r/:slug
  1. KV.get(slug) → cached link target
  2. If KV miss → D1 link row read
  3. D1 policy check: status, expiry, password, max-clicks
  4. If pass → edge redirect (not proxy)
  5. Background: Analytics Engine write (non-blocking)

This design means a password check adds one D1 read, but it does not add a round trip for analytics. The policy read remains fail-closed: missing or invalid D1 state yields 404 rather than a permissive default.

Workspace authorization

Every management endpoint begins with the same three checks:

PLATE / MERMAID REFERENCE
flowchart TD
  Request[Request] --> Session[Fresh Session]
  Session --> Workspace[Active Workspace]
  Workspace --> Permission[Resource Permission]
  Permission --> Handler[Route Handler]

Better Auth provides session validation, including a 15-minute freshness gate that cannot be extended by updatedAt renewal alone. Workspace selection is explicit: callers send workspaceId in the route, and the middleware confirms active membership before reaching the handler.

Repository methods never discover the workspace from the session. They receive workspaceId as an argument. This makes the boundary inspectable: a handler without an explicit workspace scope cannot accidentally access cross-tenant data.

D1 schema boundaries

The 34-table schema uses three organizational principles:

BoundaryTablesOwnership rule
Identityusers, sessions, accountsGlobal; Better Auth managed
Workspaceworkspaces, members, invitationsworkspaceId required on reads
Resourceslinks, domains, apiKeys, webhooks, importsworkspaceId + createdBy
BackgrounddeliveryLogs, performanceEvents, auditRecordsChannel-checked; separate retention

Workspace default_domain_id is the only default-domain source. Plan limits come from shared code configuration, not per-row values. The schema baseline 0000_initial_schema.sql initializes a complete empty database; subsequent migrations are additive only.

Non-blocking side effects

Analytics, CSV imports, and webhooks share a common pattern:

PLATE / MERMAID REFERENCE
flowchart LR
  Handler[Route Handler] --> Accept[Accept Durable Record]
  Accept --> Queue[Queue Message]
  Queue --> Consumer[Queue Consumer]
  Consumer --> Retry[Durable Object Lease]
  Retry --> VK[Webhook / R2 / D1]

The route handler writes an accepted record to D1 and enqueues a message. The Queue consumer claims a Durable Object lease, performs the side effect, and persists completion. Failures retry via Cron recovery with a 60-second cooldown. Queue send failures do not drop work; the accepted record remains eligible for later admission.

Trade-offs

  • D1 is SQLite at the edge — good for structured workspace data, not for high-frequency analytics writes. Analytics Engine handles the latter.
  • KV caches link targets for fast redirects but can become stale on expiry or password changes. The policy read revalidates current D1 state before redirecting.
  • 34 tables in one D1 database simplifies joins but requires schema discipline. Drizzle relationalQueries and explicit repository boundaries prevent ad-hoc table proliferation.

Reference