System overview
Real estate platform for Mexico (bilingual es-MX / en-US) with PostGIS geographic capabilities. Turborepo monorepo running on Bun.
Services topology
Section titled “Services topology”graph TB
subgraph Client
Browser["Browser (SvelteKit SSR + CSR)"]
Mobile["Mobile (native iOS/Android)"]
end
subgraph Dokploy["Dokploy (Hostinger VPS)"]
SvelteKit["SvelteKit web app"]
API["apps/api (Hono on Bun)"]
Worker["BullMQ worker"]
Postgres["PostgreSQL + PostGIS 16"]
Valkey["Valkey (Redis-compatible)"]
Meilisearch["Meilisearch v1.9"]
imgproxy["imgproxy v3.30"]
end
Bucket["Cloudflare R2 (S3-compatible)"]
subgraph External["External"]
Resend["Resend (Email / OTP)"]
GoogleMaps["Google Maps Platform"]
Sentry["Sentry"]
Google["Google OAuth"]
Stream["GetStream (chat)"]
Knock["Knock (notifications)"]
Replicate["Replicate (AI staging)"]
Gemini["Gemini 2.5 Flash"]
end
Browser --> SvelteKit
Browser --> API
Mobile --> API
SvelteKit --> Postgres
SvelteKit --> Valkey
SvelteKit --> Meilisearch
SvelteKit --> Bucket
SvelteKit --> Resend
SvelteKit --> GoogleMaps
SvelteKit --> Sentry
SvelteKit --> Google
API --> Postgres
API --> Valkey
Worker --> Postgres
Worker --> Valkey
Worker --> Replicate
Worker --> Gemini
Worker --> Knock
SvelteKit --> Stream
Bucket --> imgproxy
imgproxy --> Browser
Inter-service communication stays on Dokploy’s internal Swarm network (dokploy-network); the data services publish no ports. Only the SvelteKit app, apps/api, imgproxy, and the docs site have public domains (Cloudflare → Traefik). Object storage is Cloudflare R2, reached over the S3 API.
Technology stack
Section titled “Technology stack”| Layer | Technology | Version |
|---|---|---|
| Runtime | Bun | 1.3+ |
| Web framework | SvelteKit + Svelte 5 | 2.50 / 5.49 |
| Mobile framework | Swift + SwiftUI (iOS 17+) / Kotlin + Jetpack Compose (SDK 26+) | — |
| Database | PostgreSQL + PostGIS | 16 |
| Query builder | Kysely (on pg) — typed, no ORM | — |
| Auth | Better Auth — email OTP + Google OAuth | 1.4 |
| State (web) | Svelte 5 runes ($state, $derived, $effect) | — |
| State (mobile) | @Observable (iOS) / StateFlow ViewModels (Android) | — |
| Search (locations) | Meilisearch | 0.55 |
| Search (properties) | @repo/redis-search — Valkey GEOSEARCH | — |
| File Storage | Cloudflare R2 (S3-compatible) | — |
| Image Processing | imgproxy (HMAC-signed URLs) | 3.30 |
| Styling (web) | Tailwind CSS | 4.1 |
| Styling (mobile) | Generated design tokens (SwiftUI / Compose) | — |
| UI Components (web) | bits-ui (headless) + formsnap | 2.14 |
| UI Components (mobile) | Hand-built per-app primitive libraries (lockstep iOS/Android) | — |
| Forms (web) | sveltekit-superforms + Zod | 2.29 / 4.3 |
| i18n | Paraglide JS | 2.10 |
| Icons | Lucide Svelte (web) / SF Symbols + Material icons (native mobile) | — |
| Chat | GetStream.io | — |
| Notifications | Knock | — |
| Resend | — | |
| Maps | Google Maps Platform | — |
| Background jobs | BullMQ (on Valkey) | — |
| Error Tracking | Sentry | — |
| Testing | Playwright (E2E), Bun Test (unit) | 1.58 |
| CI/CD | GitHub Actions | — |
| Deploy | Docker + Dokploy (dev, Hostinger VPS) | — |
Monorepo layout
Section titled “Monorepo layout”real-estate-core/├── apps/│ ├── svelte-web/ # SvelteKit web app (public + dashboards)│ ├── ios-user/ # Native iOS customer app (Swift + SwiftUI)│ ├── ios-agent/ # Native iOS agent app (Swift + SwiftUI)│ ├── android/ # Gradle umbrella build for both Android apps│ │ ├── app-user/ # Native Android customer app (Kotlin + Compose)│ │ ├── app-agent/ # Native Android agent app (Kotlin + Compose)│ │ └── core/design-system/ # :core:design-system — generated Tokens.kt + Compose primitives│ ├── worker/ # BullMQ background worker│ ├── api/ # Standalone Hono server (tRPC + Better Auth)│ └── docs/ # This documentation site│├── packages/│ ├── api/ # Shared tRPC routers + services│ ├── contract/ # Zod wire schemas → Swift/Kotlin model codegen│ ├── database/ # Kysely client (pg) + SQL migrations + generated types│ ├── redis-search/ # Property geo-search (Valkey GEOSEARCH)│ ├── cache/ # Valkey/Redis utilities│ ├── validation/ # Shared Zod schemas│ ├── auth/ # Better Auth wiring (web + API)│ ├── notifications/ # Knock translations + email layout│ ├── server-services/ # Shared server helpers (IP-trust, rate limit, email)│ ├── eslint-config/ # Shared ESLint rules│ └── typescript-config/ # Shared TS configs│├── docs/ # Source-of-truth Markdown (mirrored by this site)├── context/ # Local-only design notes (gitignored)├── turbo.json # Turborepo pipeline└── package.json # Root workspaces ([email protected])Key design decisions
Section titled “Key design decisions”- SvelteKit over Next.js — smaller bundles, simpler reactivity (Svelte 5 runes), unified server/client.
- Valkey GEOSEARCH over Typesense — native Redis geo-filtering; Meilisearch handles location text search separately.
- imgproxy over build-time processing — on-demand resizing with HMAC-signed URLs; no pre-generation.
- Better Auth over Supabase Auth — self-hosted with full OTP control (via Resend) + Google OAuth.
- bits-ui over opinionated UI frameworks — headless, accessible primitives styled with Tailwind.
- S3-compatible object storage over Supabase Storage — endpoint-swappable (
@aws-sdk/client-s3); currently Cloudflare R2 (previously Railway Bucket), zero code changes to move. - Paraglide JS over runtime i18n — compile-time translations for zero-runtime overhead and tree-shaking.
- Portal + Dashboard split — personal user activities (
/portal) separated from agent workflows (/dashboard). - Form actions on web, tRPC on mobile — web mutations are SvelteKit form actions (progressive enhancement); mobile speaks tRPC. See Conventions → Web and Conventions → tRPC.
- Standalone
apps/api— mobile and (eventually) browser tRPC traffic go through a Hono server on port 4000 that mounts both/api/auth/*and/api/trpc/*. SvelteKit hosts the same Better Auth handler for its own web flow.