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) RealtimeDO
(dynamic SQLite) (single-instance) (auth data) (ops metadata) (WebSocket
Hibernation)

┌─────┴─────┐
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, and Realtime uses RealtimeDO.

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)


Rules ──────────── Declarative access rules (TypeScript functions, deny-by-default)


Internal Guard ─── Restricts /internal/* endpoints


Route Handler ──── Routes to D1, DatabaseDO, PostgreSQL, or RealtimeDO

The ordering is intentional. Auth runs before Context because membership verification needs the authenticated user ID. Context runs before Rules because access rules can reference context.role.

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 RealtimeDO 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 (~54 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.

RealtimeDO

Manages WebSocket connections using Cloudflare's Hibernation API:

ResponsibilityDetails
SubscriptionsCollection change notifications with server-side filters
PresenceOnline/offline user tracking per channel
BroadcastArbitrary message passing between connected clients
HibernationIdle connections cost $0, wake on message

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