Skip to main content

OdontoX Mobile — Infrastructure Isolation Design

Date: 2026-05-09 Status: Draft for review Question: Should mobile apps get their own Cloudflare Worker + Neon DB branch, isolated from web traffic?

1. Recommendation — TL;DR

Yes, create a dedicated api-mobile Worker. No, do not branch the Neon DB.
DecisionVerdictReason
Separate Cloudflare WorkerDo itIndependent rate limits, analytics, mobile-specific caching headers, deploy without touching web
Separate Neon DB branchSkip itShared data is required — mobile patients must see the same appointments as web receptionists; branching creates divergence risk
Separate DB connection poolDo itMobile Worker gets its own DATABASE_URL pointing to a dedicated PgBouncer pooler endpoint — isolates connection quota from web

2. Current Architecture

Problems with current setup:
  • Mobile and web share the same Worker → one bad mobile deploy breaks the web app
  • No mobile-specific rate limiting (patient phone hammering API degrades web for clinics)
  • No analytics separation (can’t tell mobile vs. web request counts, latency)
  • Mobile push relay, device registration, and FCM/APNs endpoints live alongside web-only endpoints
  • Can’t set different Cache-Control headers for mobile-friendly CDN caching


4. Why Separate the Worker

4a. Independent deployment

Mobile app releases are decoupled from the web deployment pipeline. Ship a mobile-only API change (/user-devices, /mobile/permissions) without touching the web api Worker. Roll back mobile API independently if a bug is found post-release.

4b. Mobile-specific rate limiting

api-mobile Worker rate limits:
- 60 req/min per JWT (prevents battery-draining polling)
- 10 req/min on /auth/login (brute force)
- 5 req/min on /auth/refresh (JWT churn)
- No rate limit on /health (for connectivity checks)

api Worker (web) has different limits calibrated for browser SPA behavior.

4c. Mobile-specific cache headers

Hono can set different Cache-Control and CDN-Cache-Control headers for mobile responses:
EndpointWeb responseMobile response
GET /clinics/:idmax-age=60max-age=86400, stale-while-revalidate=3600
GET /appointments/available-slotsmax-age=30max-age=120 (mobile user checks less frequently)
GET /mobile/permissionsn/a (web-only)max-age=3600 (changes rarely)
Cloudflare CDN caches the mobile responses at the edge — iOS/Android apps get sub-10ms responses for static-ish data from the nearest PoP.

4d. Separate analytics & alerting

Cloudflare Workers Analytics → two dashboards:
  • api Worker: web SPA latency, error rates, clinic-facing requests
  • api-mobile Worker: mobile session latency, push token registrations, device OS breakdown, mobile-specific error rates
Alert thresholds can differ — mobile 5xx rate alert at 2% (acceptable brief outage during app update rollout) vs. web at 0.5%.

4e. Service Binding — no code duplication

Shared business logic (appointment rules, billing calculations, auth validation) lives in a third core-api Worker bound as a service: Service bindings bypass the public internet — zero network cost, type-safe RPC via env.CORE_API.fetch(). No code duplication between web and mobile Workers.

5. Why NOT Branch the Neon DB

5a. Data must be shared

A patient books an appointment on the iOS app → a receptionist must see it on the web. A doctor writes a clinical note on the web → the patient sees it on the iOS app. The mobile app is an alternative interface to the same clinic data, not a separate data silo. Branching would create a copy of the database that diverges immediately upon the first write. Merging branches is a destructive operation — you cannot safely merge two branches that both had writes.

5b. Use separate connection pool endpoints instead

Neon supports multiple pooled connection strings per project. Each string gets its own PgBouncer pool:
# Existing web pool (in api Worker)
DATABASE_URL=postgres://user:[email protected]/odontox?pgbouncer=true

# New mobile pool (in api-mobile Worker only)
DATABASE_MOBILE_URL=postgres://user:[email protected]/odontox?pgbouncer=true
Same database, different connection pools. Benefits:
  • Mobile connection quota (max 50 connections) isolated from web quota
  • Mobile query patterns can be optimized independently (read-heavy, different indexes)
  • No risk of mobile traffic causing connection exhaustion on web API

6. Implementation Plan

Step 1 — Clone api Worker as api-mobile

# In existing Workers repo
cp -r workers/api workers/api-mobile
cd workers/api-mobile
# Update wrangler.toml:
#   name = "api-mobile"
#   routes = ["mobile.odontox.io/api/v1/*"]
Remove web-only routes from api-mobile:
  • /auth/magic-link (web-only email link auth)
  • /admin/clinic-settings/* (web admin panel only)
  • /reports/* (web report builder)
  • All superadmin routes
Add mobile-only routes to api-mobile:
  • POST /user-devices — FCM/APNs token registration
  • GET /mobile/permissions — mobile permission set per role
  • GET /appointments/available-slots — already exists, keep
  • PATCH /notifications/:id/read — mobile notification read state

Step 2 — Create Service Binding for shared logic

Extract shared middleware (auth validation, JWT refresh, role guard) into core-api Worker. Bind in both wrangler.toml files:
# api/wrangler.toml and api-mobile/wrangler.toml
[[services]]
binding = "CORE_API"
service = "core-api"

Step 3 — Add mobile connection pool endpoint

In Neon console: Connection Details → Add pooled connection → name it mobile-pool. Copy URL → add to api-mobile Worker secrets:
npx wrangler secret put DATABASE_MOBILE_URL --env production
Update api-mobile db connection to use DATABASE_MOBILE_URL from env.

Step 4 — Configure DNS

mobile.odontox.io → CNAME → api-mobile.odontox.workers.dev (Cloudflare proxied)
Update iOS app APIClient.swift base URL: https://mobile.odontox.io/api/v1 Update Android app ApiClient.kt base URL: https://mobile.odontox.io/api/v1 Certificate pinning must be updated with mobile.odontox.io Cloudflare-issued leaf cert.

Step 5 — Rate limiting

Add Cloudflare Rate Limiting rules on api-mobile Worker:
  • Rule 1: 60 req/60s per IP+JWT pair on *
  • Rule 2: 10 req/60s per IP on /auth/login
  • Rule 3: 5 req/60s per IP on /auth/refresh

7. Migration Sequence


8. Cost Impact

ResourceCurrentAfter isolationDelta
CF Workers requests1 Worker2 Workers (api + api-mobile)Same total — just split
CF Workers compute~same~same0
Neon connection pools1 pool2 poolsFree on Neon (multiple pooler endpoints per project)
DNS entry1 (api.)2 (api. + mobile.)Free
CertificateCloudflare autoCloudflare auto0
Total extra cost~$0
Cloudflare Workers pricing is per-request, not per-Worker. Two Workers handling the same total traffic = same cost.

9. Decision Summary