XFitness
Comprehensive gym management ecosystem integrating memberships, payments, QR-based access control, and trainer booking into a unified web + mobile platform. Consolidated multiple operational systems, reducing administrative overhead by 50%.
Tech Stack
Stakeholders
Gym Owner (Client)
Final approval on feature scope, payment gateway selection, and brand requirements
Gym Manager
Day-to-day admin dashboard user — key tester for onboarding and reporting flows
Trainers
Primary users of the mobile app for session booking and member chat
Members
End-consumers of both web portal and mobile app; QR entry and payment flows
Zafran (Lead Dev)
System architecture, full-stack development, DevOps, payment integration
The Problem
The client operated a gym chain relying on spreadsheets, physical logbooks, and manual payment receipts. New member onboarding took 15–20 minutes per person, access control was handled by staff manually checking IDs, and trainers had no dedicated channel to communicate with members.
The Solution
Architected a multi-tenant platform across web (Next.js 15) and mobile (React Native / Expo) backed by a single Supabase + PostgreSQL database with row-level security. Integrated Revenue Monster and Razorpay for payment processing, a QR-code generation system for keycard-free gym entry, and a real-time chat layer for trainer–member messaging.
Architecture
Three-tier architecture: a Next.js 15 web dashboard (admin + member portal), a React Native Expo mobile app (trainer + member-facing), and a shared Supabase backend (PostgreSQL + Auth + Realtime + Storage). All database access is governed by Supabase Row-Level Security policies — no server-side proxy needed for read paths. Write operations that require payment or QR validation go through edge functions.
- 01
Web Dashboard (Next.js 15)
App Router with Server Components for data fetching. Admin CRUD for members, packages, and reports. Member portal for booking and payment history. Deployed on Vercel with environment-scoped Supabase keys.
- 02
Mobile App (React Native / Expo)
Expo Router for file-based navigation. Trainer dashboard for session management and real-time chat. Member app for QR entry display, class booking, and payment. OTA updates via Expo EAS.
- 03
Supabase Backend
PostgreSQL with RLS policies per user role. Supabase Auth (JWT) for session management. Supabase Realtime for chat subscriptions. Supabase Storage + Cloudflare R2 for media assets.
- 04
Payment Layer
Razorpay for card payments (web). Revenue Monster for local e-wallet support (TNG, Boost, ShopeePay). Webhook receivers validate and write to the payments table inside a DB transaction.
- 05
QR Access Control
On check-in, the member's app generates a time-scoped JWT-signed QR code. The gym's tablet scanner decodes and verifies the signature + expiry server-side before granting entry.
Dev Setup
Prerequisites
- Node.js 20+
- pnpm
- Supabase CLI
- Expo CLI
- Docker (for local Supabase)
# Fill in NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, RAZORPAY_KEY_ID, etc.
# Starts local Postgres + Auth + Storage
# Applies migrations and seed data
# Starts Next.js dev server on localhost:3000
Challenges
- 01
Multi-gateway payment reconciliation
Revenue Monster and Razorpay have different webhook schemas and retry behaviours. A payment could be confirmed by the gateway but the webhook could arrive out-of-order or duplicated. Solved with idempotency keys stored in the DB — every webhook handler checks for an existing processed event ID before writing.
- 02
RLS policy complexity at scale
Supabase RLS policies are powerful but can become a performance liability with joins across many tables. Early policies were written naively and caused N+1 query patterns. Refactored using security definer functions and materialised role checks to keep query plans efficient.
- 03
QR code replay attacks
Early prototype used static member IDs as QR content — trivially screenshotted and reused. Replaced with HMAC-SHA256 signed tokens with a 90-second expiry window. Scanner validates signature and timestamp server-side before granting entry.
What I Learned
- 01
Design your RLS policies before your schema — retrofitting them is painful.
- 02
Payment webhooks are unreliable; always build idempotency into your write handlers.
- 03
Expo EAS makes OTA updates genuinely painless for iterating with clients without a full app-store review cycle.
- 04
Supabase Realtime is easy to get started with but needs careful channel management to avoid memory leaks in React Native.