One React codebase serves every platform. A Node API owns all business rules and talks to Postgres; the clients never touch the database. Services that hold secrets or money logic sit behind the API.
Clients
Web app (PWA)React 18, Vite, Tailwind CSS 4 and a custom design system with dark and light themes
iPhone and AndroidThe same build packaged with Capacitor 8, plus a native iOS App Intent for Siri quick add
Offline layerIndexedDB read cache and an ordered outbox with duplicate-safe replay
HTTPS with bearer-token sessions
API
Node and ExpressAbout 98 routes. Auth with bcrypt, passkeys (WebAuthn) and TOTP. Sessions stored only as SHA-256 hashes
Money rulesInteger-cent splits, settlement locks and one shared function for “my share”
PushSent straight to Apple (APNs over HTTP/2) and Google (FCM v1), without a third-party SDK
Server-side only
Data and services
Postgres 16Row locks and transactions around invites, sign-ups and settlements; nightly encrypted, restore-checked backups
Receipt storageCloudflare R2, encrypted by the API with AES-256-GCM before upload
Email and FXResend for account email; Frankfurter for reference exchange rates, labelled approximate
Hosting started on Vercel, Render and Neon. It now runs as a self-hosted Docker Compose stack (API, web server, Postgres) published through Cloudflare, which gave me control over cost, backups and data location.