System at a Glance
A social media platform built for staying in touch with people you know — no ads, no data sales, no virality mechanics. The entire backend runs on Cloudflare's edge network: Workers handle compute, D1 provides shared SQLite shard databases, R2 stores media uploads, and KV caches profiles and shard routes.
Edge-First
Every request is served from the nearest Cloudflare data center. No origin server, no cold starts.
Shared D1 Shards
Users are assigned to a shared D1 shard via load balancing. All their posts, friends, and inbox live in that shard.
Time-Bounded
Posts expire automatically — free tier media lasts 48 hours, text 30d. Paid tiers get 90–180 days.
Technology Choices
Every piece of the stack runs on Cloudflare's infrastructure. No external databases, no third-party servers, no AWS dependencies.
Edge Architecture
When a user opens the app, their request hits the nearest Cloudflare data center (300+ cities worldwide). The Worker handles routing, auth, and business logic. Data queries go to D1 in the same region — single-digit millisecond latency.
Media uploads go through the Worker to R2 directly — the Worker reads the file body and writes to R2. There is no presigned URL flow; the Worker is the intermediary.
Shared D1 Shards
Users are assigned to a shared D1 shard database (e.g. SHARD_FREE_001).
Each shard stores data for many users — the findAvailableShard function picks the shard with the lowest user count (load balancing). The Admin DB
(separate D1) holds accounts, subscription plans, feature flags, RBAC, and billing.
User Shard Tables
Admin DB Tables
+ several billing/RBAC/feature-flag tables
Shard Routing
getUserDatabaseId(userId)user:{userId} → { databaseId, shardId, tier }resolveShardBindingAsync(databaseId)local-dev-free-001 → SHARD_FREE_001){ db: drizzle(d1), userId, d1 }3 shards per environment (dev/qa/prod): SHARD_FREE_001/002/003.
New shards are staged with maxUsers: 999999 — no hard cap on users per shard.
Letterbox Pattern
When you post, the system writes a copy to each friend's inbox (write-time fan-out).
Reading your feed is a single query against your own Inbox table — no joins, no cross-shard lookups. Each inbox entry contains a denormalized
snapshot of the author's name, avatar, post content, and media references, so feed
reads never touch the author's D1 shard.
Fan-Out
Currently synchronous and inline — all friends processed in a loop. MAX_SYNC_FANOUT = 100 is defined but not enforced. No async queue or Durable Object path is wired.
Profile Cache
KV stores { displayName, avatarR2Key, tier } under user:{userId} for friend lists and recommendations. Feed reads use the denormalized
authorSnapshot baked into inbox rows — not the KV cache.
R2 Object Storage
Media uploads (photos, videos) go to Cloudflare R2 — zero egress fees, same-region as the user's D1 shard. The Worker receives the file and writes it to R2 directly (no presigned URL flow).
Max Media Size
50 MB
per upload
Max Avatar Size
5 MB
resized to 256×256 client-side
Storage Quota
500 MB – 20 GB
by tier
R2 Path Convention
media/{userId}/{mediaUuid}.{ext}avatars/{userId}/avatar.{ext}covers/{userId}/cover.{ext}Allowed: JPEG, PNG, GIF, WebP (images) + MP4, WebM (video)
Oldest-first eviction: when a user hits their storage quota, the oldest unlocked media is automatically deleted. Locked media is exempt from eviction. Avatars are center-cropped to 256×256 and converted to JPEG @ 90% quality on the client before upload. Covers are resized to 1200px width.
Subscription Tiers
Three tiers with time-based post expiry, storage quotas, and media capabilities.
Defined in plan-service.ts and seeded into the Admin DB via seed-core.sql. expiresAt is stamped at
creation and is immutable — downgrading does not change existing posts' expiry.
| Feature | Free ($0) | Basic ($1/mo) | Pro ($2/mo) |
|---|---|---|---|
| Text post retention | 30 days | 90 days | 180 days |
| Media post retention | 48 hours | 90 days | 180 days |
| Storage quota | 500 MB | 5 GB | 20 GB |
| Photo uploads | ✓ | ✓ | ✓ |
| Video uploads | ✓ | ✓ | ✓ |
| Lock media | — | ✓ | ✓ |
Free tier allows photo and video uploads — media posts just expire after 48 hours. Paid tiers get 90–180 day media retention. Locked media survives post expiry (the post reference is set to NULL but the media file remains in R2 and counts toward quota).
Authentication
Password Hashing
PBKDF2-SHA256 with 100,000 iterations via Web Crypto subtle.deriveBits.
32-byte derived key, 16-byte salt. Hash format: pbkdf2$100000$saltHex$hashHex.
Constant-time comparison. No bcrypt (not available in Workers runtime).
JWT Tokens
HS256 (HMAC-SHA256) access tokens: 15-minute TTL. Opaque refresh tokens: 30-day TTL (32 random bytes, stored in RefreshToken table). Refresh token rotation on use — old token revoked, new one issued.
Bearer Header Auth
API uses Authorization: Bearer header.
No cookies, no Set-Cookie.
CSRF disabled (header-based auth, not form/cookie). All UI uses apiClient (not raw fetch) —
auto-attaches Bearer token, 401 → refresh → retry with concurrent refresh prevention.
RBAC & 403 Gating
Admin endpoints double-gated: requireAdmin (checks admin.access permission) then requirePermission (e.g. users.read, roles.create).
Both return 403 with audit log on failure. User-facing endpoints use requireAuthenticated (401-only).
RESTful API
All endpoints are Cloudflare Pages Functions deployed alongside the frontend.
JWT-authenticated via Bearer header. Admin endpoints are 403-gated by role/permission.
All UI uses apiClient (not raw fetch).
94 endpoint files across auth, posts, feed, friends, media, profile, notifications, account/billing, admin (users, roles, plans, feature flags, shards, settings), and Stripe webhook. Key endpoints below:
| Method | Path | Description |
|---|---|---|
| POST | /api/auth/register | Create account + assign shard |
| POST | /api/auth/login | Login → access + refresh tokens |
| POST | /api/auth/refresh | Rotate refresh token, new access token |
| POST | /api/auth/logout | Revoke refresh token |
| POST | /api/auth/delete-account | Delete account + data |
| GET | /api/posts | List own posts |
| POST | /api/posts | Create post (text or media) |
| GET | /api/posts/:uuid | Get a single post |
| DELETE | /api/posts/:uuid | Delete a post |
| POST | /api/posts/:uuid/comments | Comment on a post |
| POST | /api/posts/:uuid/react | React to a post |
| GET | /api/feed | Read inbox (feed) — letterbox pattern |
| GET | /api/friends | List friends |
| GET | /api/friends/:userId | Follow / unfollow a user |
| GET | /api/friends/recommendations | Friend-of-friend suggestions |
| GET | /api/friends/search | Search for users |
| GET | /api/notifications | List notifications |
| POST | /api/media/upload-urls | Upload media to R2 via Worker |
| POST | /api/media/:uuid/lock | Lock media (prevent eviction) |
| GET | /api/profile/:userId | Get user profile |
| PUT | /api/profile | Update own profile |
| POST | /api/profile/avatar | Upload avatar (resized 256×256) |
| POST | /api/account/subscribe | Subscribe to a plan |
| GET | /api/account/usage | Get storage usage |
| POST | /api/stripe/webhook | Stripe webhook (billing) |
| GET | /api/admin/users | Admin: list users (403-gated) |
| GET | /api/admin/shards | Admin: list shards (403-gated) |
| GET | /api/admin/feature-flags | Admin: list feature flags (403-gated) |
| GET | /api/admin/plans | Admin: list subscription plans (403-gated) |
Showing 29 of 94 endpoint files. Full list includes admin CRUD for roles, permissions, email campaigns, audit logs, provisioning, database stats, and more.