
Restore Wellness - In-Home Massage Marketplace
The Challenge
This isn't a single app. It's a marketplace with three roles (client, therapist, admin) sharing one codebase, plus real money moving between strangers. The hard parts stack up: a booking has to be handed to exactly one therapist even when several tap "Accept" at the same instant; a client pays the business while each therapist gets paid out to their own account; identity has to be verified for in-person safety; and the whole thing has to clear Apple review before a single user can install it.
The Solution
Built a React Native app on Expo Router where the same binary serves clients, therapists, and admins, with the UI a user sees gated by their Supabase auth role. Supabase Postgres with Row Level Security is the security boundary, so a client can never read another client's bookings even if they hit the API directly. Around 30 Deno edge functions run everything that must stay server-side: Stripe Checkout for client payments, Stripe Connect for therapist payouts and refunds, the Stripe webhook, Resend transactional email at every step, and Expo push notifications. Booking assignment uses an optimistic lock that only writes if the row is still unassigned, so the first therapist to tap "Accept" wins and the rest get a clean "already taken" instead of a double-booking.
Architecture
Impact
- Live on the Apple App Store, having passed Apple review including required account deletion and reviewer demo accounts
- One React Native codebase serves three roles (client, therapist, admin) off a single Supabase auth model
- Stripe Connect marketplace flow: client pays the business, each therapist is paid out to their own connected account, admin issues refunds
- Optimistic-lock booking assignment guarantees exactly one therapist claims each request under concurrent taps
- Row Level Security enforces per-user data isolation at the database, not just in the UI
- End-to-end lifecycle automated over ~30 edge functions: matching, arrival, SOAP clinical notes, review requests, and provider compliance/onboarding
What I Learned
- An optimistic lock (write only if still unassigned) was a far simpler and more reliable way to prevent double-booking than a queue or a distributed lock
- Apple review shapes the build: account deletion, working demo accounts, and payments in Stripe test mode all had to exist before submission, so planning for them late would have cost a rejection cycle
- Row Level Security is worth the upfront effort because it makes the database the source of truth for authorization, so a missed client-side check can't leak another user's data
- Pushing every payment and email call into Supabase edge functions kept Stripe and Resend secrets off the device entirely, which the client app has no business holding