Skip to main content

Architecture Overview

How EdgeBase processes every request — from HTTP entry to the correct storage backend.

High-Level Architecture

Client SDK ──HTTP/WS──▶ Worker (Hono)

┌─────────────────┼─────────────────┬─────────────┐
│ │ │ │
DatabaseDO D1 DB Blocks D1 (AUTH_DB) D1 (CONTROL_DB) DatabaseLiveDO RoomsDO LogsDO
(dynamic SQLite) (single-instance) (auth data) (ops metadata) (DB subs & (rooms, (structured
broadcast) presence, logging)
broadcast ch.)

┌─────┴─────┐
R2 KV
(File storage) (Cache, OAuth,
Push Tokens)

Every request enters through a single Cloudflare Worker running Hono, which routes to the appropriate backend. Dynamic DB blocks use DatabaseDO with embedded SQLite, single-instance DB blocks default to D1, auth uses AUTH_DB, plugin/control-plane metadata uses CONTROL_DB, DB subscriptions and server broadcast use DatabaseLiveDO, rooms/presence/broadcast channels use RoomsDO, and structured logging uses LogsDO.

Request Lifecycle

Middleware Chain

Every HTTP request passes through a strict middleware chain before reaching any backend:

Request


Error Handler ─── Global error boundary, catches all exceptions


Logger ─────────── Request/response logging


CORS ───────────── Cross-origin resource sharing


Rate Limit ─────── 2-tier: software counter + Cloudflare Binding ceiling


Auth ───────────── JWT verification (local crypto, no DO call) [/api/*]


Rules ──────────── Declarative access rules (TypeScript functions, deny-by-default) [/api/db/*]


Internal Guard ─── Restricts /internal/* endpoints [/internal/*]


Route Handler ──── Routes to D1, DatabaseDO, PostgreSQL, DatabaseLiveDO, or RoomsDO

The ordering is intentional. Auth runs before Rules because access rules need the authenticated user ID to evaluate per-record permissions.

Request → DB Block Routing

The Worker determines which backend to contact based on the DB block configuration:

GET /api/db/shared/tables/notes?filter=...

Worker:
1. JWT verify (local crypto) → extract auth.id
2. Resolve backend + database identity:
- single-instance DB → D1 binding by default (e.g. DB_D1_SHARED)
- dynamic DB → DO name "user:{userId}" | "workspace:{wsId}"
- postgres/neon → Worker-side PostgreSQL handler
3. Route request to the resolved backend

Durable Object Types

DatabaseDO

The workhorse for dynamic DB blocks. Each instance owns an embedded SQLite database.

ResponsibilityDetails
CRUD operationsCreate, read, update, delete with filtering, sorting, pagination
Schema managementLazy table creation on first request, automatic migrations
Full-text searchFTS5 powered, configured per table
Access rulesEvaluated per record using TypeScript functions
Event emissionNotifies DatabaseLiveDO on data changes

Naming convention: {namespace} (static, e.g. shared) or {namespace}:{id} (dynamic, e.g. user:abc123, workspace:ws_456)

A single DatabaseDO processes requests serially (single-threaded). This eliminates concurrency bugs but bounds throughput to ~200-1,000 writes/sec per instance. The DB-block pattern (user:{id}, workspace:{id}, etc.) distributes load across many independent DO instances.

Auth (D1-First)

All authentication is handled by D1 (AUTH_DB) directly — no Durable Objects involved:

D1 (AUTH_DB) ── All auth data (users, sessions, OAuth, MFA, passkeys)
D1 (CONTROL_DB) ── Internal control-plane metadata (plugin versions, cleanup state)

The Worker routes auth requests directly to D1 via auth-d1-service.ts (~61 functions). The Auth DO binding (AUTH) exists only as an empty shell for Cloudflare migration compatibility (returns 410 Gone).

Key design decision: Data requests verify JWT locally and then go straight to the configured DB backend. No separate auth infrastructure hop is needed first.

DatabaseLiveDO

Manages real-time DB subscription streaming and server-side broadcast via Cloudflare's Hibernation API:

ResponsibilityDetails
SubscriptionsCollection change notifications with server-side filters
Server broadcastHTTP-based broadcast via /api/db/broadcast
HibernationIdle connections cost $0, wake on message

RoomsDO

Server-authoritative real-time state channel for multiplayer games, collaborative editing, and live dashboards:

ResponsibilityDetails
Room stateShared, player, and server state areas
MembersOnline/offline user tracking per room (replaces presence)
SignalsArbitrary message passing between connected clients
HibernationIdle rooms cost $0, state persisted to DO Storage

Auxiliary Storage

ServiceRoleExamples
D1Auth database (AUTH_DB)All auth data: users, sessions, OAuth, email tokens, MFA, passkeys, public profiles
D1Internal control plane (CONTROL_DB)Plugin versions and other internal operational metadata
KVEphemeral state cacheOAuth state (300s), WebSocket pending (10s), multipart upload tracking (7d), email tokens, push tokens/logs
R2File storageUser uploads, signed URLs, multipart uploads

All KV entries are TTL-based and self-healing — they regenerate naturally after expiration or data loss, requiring no backup.

Three Deployment Modes

The same codebase runs identically across three environments because Cloudflare's workerd runtime is open source:

ModeCommandRuntimeBest For
Edgenpx edgebase deployCloudflare WorkersGlobal, ~0ms cold start
Dockernpx edgebase docker runworkerd in containerSelf-hosted, full control
Node.jsnpx edgebase devworkerd via MiniflareDevelopment, VPS

All state persists in a single /data directory (Docker) or Cloudflare's infrastructure (Edge). No external database server is required.

Next Steps