Web · 2026
GymOS
A full-stack gym operating system — a Django + DRF API, a bilingual React admin dashboard, and a Flutter member app — covering members, subscriptions, check-in, POS, payroll, and reports across multiple branches.
Problem
I'd already shipped a gym management system and run it in production for a real gym for over a year. It worked — but it was single-branch, English-field- names-and-Arabic-labels tangled together, and it had grown one feature at a time. GymOS is the ground-up rewrite: the same proven domain, rebuilt to be multi-branch, role-aware, bilingual to the core, and split across a web dashboard and a member phone app sharing one API.
Architecture
One Django 5.1 + DRF backend (JWT auth) is the single source of truth for two
clients: a React admin dashboard and a Flutter member app. Twenty-two models
across a dozen focused apps — members, subscriptions, attendance, shop,
financials, staff, branches, notifications — with every scoped entity carrying
a branch foreign key. A single gym gets one default branch and never thinks
about it; an owner with several manages them all behind a branch switcher, and
reports roll up.
Access is role-based — owner, manager, receptionist, trainer — enforced by DRF
permission classes and branch-scoped querysets, then mirrored in the UI so each
role only sees the actions it's allowed. Receptionist report access is
owner-configurable per user via a module_permissions flag.
What it does
- Members & cards — profiles with optional health/fitness and referral data; each member gets a human-readable code rendered to QR and Code-128 for a physical or in-app card.
- Subscriptions — main, add-on, and locker plans that expire by time or
by session count (the proven
is_durationdesign), with freeze/unfreeze, guest-invitation passes, student pricing, and a per-subscription trainer commission override. - Check-in — scan the barcode/QR or search by name, resolve the active subscription, and warn-but-allow on expired/frozen while still logging the visit; guests check in against an invitation code. The dashboard shows a live occupancy count.
- Shop (POS) — catalog, stock, categories, and offers; confirming a sale auto-posts an income transaction and decrements inventory, with low-stock alerts.
- Financials & payroll — incomes/expenses and categories, plus salaries built from base + bonuses + deductions and a trainer's PT commission computed from that month's private-session subscriptions.
- Reports — financial, membership, attendance, and shop, all filterable by branch and date.
- Member app — a Flutter companion mirroring the same API: onboarding and login, a home dashboard with an active-subscription progress ring, the membership card for check-in, subscription and order history, a store with a persisted cart and checkout, guest invitations to share, news, profile and body stats, and push notifications.
Key decisions
- Fresh project, ported models. Rather than migrate the old system, I started clean and re-implemented the model designs that had earned their keep — fixing the debt (multi-branch, clean English field names, per-subscription commission) that a live database made hard to change.
- Bilingual as a foundation, not a coat of paint. Arabic (RTL) and English (LTR) are first-class on both clients, and the gym's name, logo, and default language are pulled from the backend so branding is data, not a rebuild.
- One API, two clients. DRF from day one meant the Flutter member app and the React dashboard consume the exact same surface — no second backend, no drift.
- Warn, don't block. A front desk can't have the software argue with a paying member at the door. Expired and frozen check-ins are allowed and logged; the judgment stays with the staff.
Stack
Django 5.1 + DRF + SimpleJWT, qrcode/python-barcode, Pillow, and
firebase-admin for FCM push. The dashboard is React 19 + TypeScript on Vite
with Ant Design, Tailwind, and Redux Toolkit (RTK Query). The member app is
Flutter with Riverpod 2, Dio, and GoRouter.
Outcome
The dashboard is deployed and demo-able, the API covers the full domain, and the member app is built end-to-end. It's the most complete thing I've built — a real operational system, rebuilt the way experience said it should have been the first time.