All projects
Mobile App
Live in production

Trainr — Two-Sided Fitness Marketplace App

Trainr is a two-sided fitness marketplace for the UAE. We built the React Native app for clients and trainers, two Node.js backends and the admin console: bookings, packages, Stripe payments and payouts, workouts, diet plans, check-ins and chat. The Android app is live on Google Play.

Updated

For TrainrMay 2025 – Aug 2026
  • Android app (Google Play)
  • iOS app (taken through TestFlight; released by Trainr's team)
  • Superadmin console (web)
  • Mobile and web backends
Trainr store screenshot showing the app home and a trainer profile card

Overview

Overview

Trainr connects people in the UAE with personal trainers, and one React Native app serves both sides. Clients find a trainer by distance, buy a package or book a single class, pay by card, and follow the workout and diet programs their trainer writes. Trainers sell services, packages and classes, run their calendar, review client logs and check-ins, chat with clients and groups, and request payouts. Trainr's operations team runs the platform from a Next.js superadmin console.

CountryDevs built the app, the mobile backend (bookings, payments, realtime chat, scheduled jobs), the web backend (ledger, payouts and admin tools) and the console. Both backends share one MongoDB database. Between May 2025 and May 2026 the platform grew to 199 screens and page routes and 978 backend HTTP endpoints; release work and the handover ran on to August 2026.

The Android app, fit.trainr.app, is in production on Google Play and takes live Stripe payments. We took the iOS build through TestFlight; Trainr's iPhone app was released by Trainr's team after our handover. trainr.fit is Trainr's own WordPress marketing site, which we did not build. The phone screenshots come from Trainr's store listing, test accounts and a local debug build on demo data; the console screenshots come from a local copy running on demo data. On 13 August 2026 we handed the platform to Trainr's incoming developer.

The story in three steps

From the problem to what runs today

  1. 01 · Where it started

    The challenge

    A trainer marketplace has two kinds of customer with different jobs. Clients want to find someone nearby, pay once and know what to do in each session. Trainers want to sell the way they already work, with monthly packages, drop-in classes, online or in person, and then coach: write programs, see what clients actually did, and get paid. Each of those jobs needs its own screens, and all of them share the same bookings, sessions and money.

    Read the full challengeShow less

    Money was the hard part. A client can buy a package, upgrade it, downgrade it or cancel a class, and each change can send money back. Trainr takes a commission, trainers are paid later, and a refund can arrive after a trainer's earnings have already been counted. The platform had to know at any moment what each trainer is owed and what the platform has earned, and an operator had to be able to check it.

  2. 02 · What we built

    The solution

    We built one React Native app with separate navigators for clients and trainers rather than two apps, so both sides share one codebase, one login and one chat. Behind it sit two Express backends on one MongoDB database. The mobile backend owns everything a phone touches: bookings, Stripe, Socket.IO chat with Redis for presence, caching and unread counts, and nine scheduled jobs for subscriptions, attendance, check-ins and settlement. The web backend serves the superadmin console: users and trainer certifications, log audits, moderation, the ledger and payouts.

    Read the full solutionShow less

    The key decision was a single unified ledger. Every sale and every refund writes a linked group of entries for the client, the trainer and the platform that must sum to zero, and a reconciliation check lists any group that does not. Stripe events arrive at one webhook on one backend, so no event is processed twice.

  3. 03 · Where it stands

    The impact

    What exists now: the TRAINR app on Google Play (package fit.trainr.app), in production since version 1.0.3 with live Stripe payments; the iPhone app, which we took through TestFlight and Trainr's team released after our handover; the superadmin console at app.trainr.fit; and both backends in production on Trainr's own server. The Play listing was last updated on 27 August 2026.

    Read the full impactShow less

    In July 2026 Trainr commissioned a review of the code. We checked all 27 of its claims against the source and answered each with a verdict and a time to close. In August we switched payments to live Stripe, moved error monitoring to Trainr's own Sentry organisation, fixed the iOS signing problem that had blocked TestFlight uploads since May, and handed over accounts, signing material, deploy and rollback runbooks, environment templates for all four repositories and a full export of the media bucket.

Chapter 1

Packages, refunds and a ledger that has to balance

Trainers sell three things: services, packages and individual classes. A duration-based package starts from a monthly price and a number of sessions a month; the trainer offers longer durations at a discount, and the server works out the sessions included, the total and the price per session. Clients can later upgrade, downgrade or add to a package, and a downgrade or a cancelled class can mean a refund.

Android Subscribe to Package screen with the 3-month option selected, a 6-month option saving AED 660, included services and a 'Subscribe for 3135 AED' button
Choosing a package length before paying: each duration shows sessions, the price per session, the effective monthly price and the saving, and the button carries the total. The capture stops here, before the Stripe payment sheet.

One webhook, one consumer

Clients pay by card through Stripe. Stripe sends three events (payment succeeded, payment failed, charge refunded) to a single webhook on the mobile backend. The web backend has no webhook on purpose: both backends write to the same database, so a second consumer could process the same event twice. A refund already recorded under its Stripe refund ID is never booked again.

The unified ledger

In January 2026 payment records were spread across a payments collection and two purchase histories, nothing tracked platform revenue, and refunds reversed nothing. We replaced that with one ledger for clients, trainers and the platform:

Event Client Trainer Platform
Sale − price + price less commission (pending) + commission
Refund + amount − amount less commission − commission

An event's entries share a transaction group ID. A reconciliation check groups the ledger by that ID and lists any group more than one fil away from zero. Trainer credits stay pending until their settlement date, so a refund inside that window still reverses cleanly, and a daily job settles what is due. Each entry stores the commission rate it used, so a rate change applies forward only.

Payouts

Trainers request a payout from their settled balance. An operator approves or rejects it in the console, then releases it with a bank transfer, PayPal or cash reference recorded; a Stripe Connect transfer path is built for when trainers' Stripe accounts are connected. A release that fails marks the payout failed instead of losing it.

Unified ledger with summary counters and transaction rows
A double-entry style ledger: client debits, trainer credits net of commission, platform commission revenue and payouts, each with settlement status.

Chapter 2

Keeping chat consistent across MongoDB, Redis and Socket.IO

Trainr has three kinds of chat: one-to-one, groups and trainer-run communities. MongoDB is the record for every room and message, Redis holds what has to be fast or shared between server processes, and Socket.IO carries the live events. Most of the work was keeping the three in agreement.

Android chat screen between a trainer and a client with read ticks and a typing indicator
A trainer and client rescheduling a session in real time: read receipts on the trainer's messages and a typing indicator from the client, whose side ran from a second connection.

On the server

  • Shared rooms. Socket.IO runs on the Redis adapter, so a message emitted by one server process reaches sockets held by another.
  • Presence that expires. Online users sit in a Redis set, and each user's presence record expires after five minutes unless a heartbeat renews it, so a dropped connection cannot leave someone online for good.
  • Unread counts. Sending a message used to run an aggregation for every other participant: 20 per message in a 20-person group. Now each user and room has a Redis counter, seeded once from MongoDB, incremented on each message and cleared when the room is read, so the next message re-seeds from the database. If Redis fails, the code falls back to the aggregation.
  • Fewer queries. Marking a room read went from one query per message to a single updateMany, and sending a message from five database queries to three, with notifications sent in parallel. Cached rooms and unread totals are deleted on every send and read, so chat lists do not show stale previews.
  • Typing. Typing events are rate-limited per user on the server.

On the phone

  • A peer's typing used to re-render the whole 1,500-line chat screen. The typing indicator now subscribes to its own state.
  • The chat list re-rendered on any of twenty-plus socket event types; its selector now reads nine fields.
  • Removing a listener with .off(event) and no handler was removing that event's listeners across the whole app. Handlers are now stored and removed one by one.

A member removed from a group keeps the conversation up to that point, read-only.

trainr.fit section 'Grow your coaching business' with an expandable feature list
An accordion lists trainer tools: packages, online/in-person coaching, scheduling, check-in progress tracking and community chat.

Chapter 3

Trainer tools: programs, a rest timer, diet plans and check-ins

Coaching happens between sessions, so most of the trainer side is about writing plans and reading what clients did.

Android screen of a client's Day 1 workout log showing target vs completed sets with RPE, trainer notes on Front Squat and trainer feedback
A trainer reviewing a client's logged session: each set's reps, weight and RPE sits under the program's target, with a per-exercise note and day feedback saved from the app.

Workout programs

A program is linear or cycling. A cycling program repeats a cycle of 1 to 14 days, up to 90 days in all (the limits the trainer app enforces), and each client enrols on their own start date; the server works out which day of the cycle they are on, and logs from earlier cycles stay in their history. Each exercise carries target sets, reps, weight and rest. Clients follow a session step by step, with a rest timer set from the trainer's rest target, or log it by hand. Every set records actual reps, weight, rest and effort (RPE 1–10) against the targets, and the trainer replies on the log.

Libraries

The app ships a system catalogue of 250 exercises and 100 meals as versioned seed files. Trainers clone what they need into their own library and edit it; a batch clone never stops on a name clash, it adds a suffix.

Diet programs

Trainers build diet programs from their meals, with calories, protein, carbs and fats. Clients log each day, and trainers answer with written nutritional adjustments on the log.

Check-ins

Each package can carry a check-in form that repeats daily, weekly or monthly. The trainer switches on the fields they want from thirteen: body weight, measurements, energy, sleep quality, sleep hours, stress, mood, motivation, soreness, nutrition, hydration, notes and photos. Clients who buy the package are added automatically, the server tracks each client's next due date, and the client's home screen flags an overdue check-in. The trainer's home screen counts check-ins waiting for feedback.

Dates

Workout sessions and individual classes once handled dates differently, one in UTC and one in local time, and classes could land on the wrong day. Every date-sensitive path (logs, bookings, check-ins, schedules) now uses UTC days.

Android Check-In Details screen showing body weight, chest and waist measurements, mood, energy and sleep ratings
A client's weekly check-in as the trainer sees it, built from the fields the trainer switched on for the package: body weight, measurements, mood, energy and sleep quality.

Chapter 4

Finding a trainer by distance

Five kinds of record carry coordinates in Trainr: a trainer's profile, and each service, workout session, class and event. A trainer can keep their own location private.

Android Trainer Filters sheet with 'Use my saved location – Downtown Dubai' selected, a 10 km radius and two location switches
The trainer filter on the Explore tab: search from a saved location with a 5, 10, 25 or 50 km radius (or Any), match on where trainers run sessions as well as their own location, and optionally include online-only trainers.

What the client sees

On the Explore tab the client picks a centre point, saved or custom, and a radius of 5, 10, 25 or 50 km, or Any. A switch decides what counts as near: the trainer's own location, or the nearest place they actually train clients across their services, sessions and classes. Results come back sorted by distance, and each carries the distance and the kind of place it was measured to, so the app can say a trainer is 2.3 km away from one of their sessions rather than just 2.3 km away. The result cards don't display it yet. Trainers with no public location, online-only or private, are left out unless the client turns on "include online trainers"; they then follow the nearby list in alphabetical order.

How the server does it

With a location set, the server scores the whole trainer list before paging, because otherwise page one would not hold the nearest trainers. It fetches the locations of every trainer's sessions, services and classes in three grouped queries, not three per trainer, computes the haversine distance from the centre to each candidate point, keeps each trainer's nearest, filters by radius, sorts and then pages the merged list. Without a location, the cheap paged query runs as before.

Why not a geospatial query yet

MongoDB's $nearSphere needs GeoJSON points with a 2dsphere index. Trainr's coordinates are stored as latitude and longitude fields on five models, and moving them means a backfill across all of them. While the trainer list is small enough to score in memory, we shipped haversine and wrote down the next step: a GeoJSON point beside each location, filled in as records are saved, a 2dsphere index and $geoNear.

Testing it

A development script spreads test trainers across Dubai neighbourhoods, including Downtown, Dubai Marina, Jumeirah, Business Bay, Al Barsha, Deira and JBR, between 0 and about 25 km from Downtown Dubai, so each radius chip had real differences to sort.

Android Guided Workout step for Bent-Over Row with target 12 reps at 37 kg and the set form filled in
A step in the client's guided workout: the trainer's target for the exercise, quick buttons (Too Easy, Hit Target, Too Hard) that prefill the set, and fields for reps, weight and a note.

Chapter 5

Release, the client's code review and the handover

Long Android screenshot of the Trainr member home screen
Full-length member home from a test account: check-in reminder, counters for sessions, logs and trainers, upcoming classes and weekly consistency.

Release

  • Android. Versions 1.0 to 1.0.2 went to closed testing; 1.0.3 was the first production build with live Stripe keys. Each release is tagged in git. Native libraries are limited to arm64-v8a and armeabi-v7a, checked on the built bundle.
  • Readable crashes. Sentry source maps upload during the release build. When Sentry moved to Trainr's own organisation, we pulled the JavaScript bundle out of the built app and searched it: the new DSN once, the old one nowhere.
  • Leaner release builds. 540 console calls were stripped from release builds; errors and warnings stay for Sentry.
  • Live payments. We checked the shape of every key before building and caught a Stripe secret key pasted into the Google Places key field. Shipped, it would have been readable inside every installed copy of the app.
  • iOS. Uploads had failed since 25 May with a signing error that looked like a permissions problem. The App Store Connect API showed the app record had always been fit.trainr.mobile; a brand rename had changed the iOS identifier to match Android's. We reverted it, added six privacy purpose strings Apple required, and build 22 reached TestFlight.

The technical review

On 27 July 2026 Trainr shared a source-level review with 27 claims in four severity bands. We checked every claim at the exact file and line it cited and answered each with a verdict and a time to close; none was dismissed without a code-level check. We also reported a defect the review had missed: account-deletion text that cited Indian tax law and INR retention periods in a UAE product. Two items were new features rather than defects, push notifications and UAE VAT, and for VAT we listed the decisions needed first: tax-inclusive or exclusive prices, the 5% rate, trainers above and below the AED 375,000 registration threshold, and TRN collection.

Handover

On 13 August 2026 the platform passed to Trainr's incoming developer with a written record of every system's state, the open items and whose court each sits in.

Two columns on trainr.fit: one for trainers and one for members, each with a download button
The home page splits the pitch into the trainer side (sell services, build a brand) and the member side (find a trainer, train with structure).

By the numbers

  • 199Screens and page routes across the app and console
  • 978Backend HTTP endpoints across the two APIs
  • 27Claims in the client's technical review, each checked at file and line
Features

What we built into Trainr

Trainer search by distance

Clients search within 5, 10, 25 or 50 km of a saved or custom point, measured from the trainer's own location or from the nearest place they train clients. Results come back sorted by distance; the API also returns each distance and where it was measured from, though the result cards don't show it yet.

Packages with duration discounts

A trainer sets a monthly price and sessions per month, then offers longer durations at a discount; the server works out the sessions included, the total and the price per session. Fixed packages, add-ons, upgrades and downgrades are supported too.

Classes with capacity and a waitlist

Individual classes recur daily, weekly, fortnightly or monthly, run online, in person or hybrid, and track capacity per date with a waitlist and a cancellation window.

Stripe payments and refunds

Card payments for packages, classes, upgrades and add-ons go through Stripe, with refunds for downgrades and cancelled classes. A refund already recorded under its Stripe refund ID is never booked twice.

Unified ledger and trainer payouts

Every sale writes client, trainer and platform entries that must sum to zero. Trainer earnings settle after a holding period, and operators approve, release or reject payout requests in the console.

Workout programs with a rest timer

Programs are linear or cycling (a 1–14 day cycle, up to 90 days in the trainer app). Clients follow a guided session with a rest timer or log by hand, and each set records reps, weight, rest and RPE against the trainer's targets.

Diet programs and a meal library

Trainers build diet programs from their meal library with calories and macros per meal. Clients log each day, and trainers answer with written nutritional adjustments on the log.

Scheduled check-ins

A package can carry a daily, weekly or monthly check-in form built from 13 optional fields, from body weight and sleep to mood and progress photos. The client's home screen flags an overdue check-in.

Realtime chat and communities

One-to-one, group and community chat over Socket.IO, with presence, typing indicators, read receipts, media, pinning, muting, blocking and group roles. A member removed from a group keeps read-only history.

Superadmin console

A Next.js console for users and trainer certifications, packages and programs, workout, diet and check-in logs, chat rooms and communities, reports, payments, the ledger, payouts and an account-deletion audit with a 60-day grace period.

Real screens · 49

What Trainr looks like

Mobile app 29

Project details
Tech stack
React NativeTypeScriptRedux ToolkitNode.jsExpressMongoDBMongooseRedisSocket.IONext.jsStripeAWS S3SentryGoogle Places API
Timeline & team
May 2025 – Aug 2026
FAQ

Questions about this project

How does Trainr keep trainer earnings, commission and refunds in balance?

Every sale and refund writes a group of ledger entries for the client, the trainer and the platform that must sum to zero, and a reconciliation check lists any group that does not. Trainer earnings stay pending until their settlement date, so a refund in that window reverses them cleanly.

Can clients find personal trainers near a chosen location in Trainr?

Yes. On the Explore tab a client picks a saved or custom point and a radius of 5, 10, 25 or 50 km, measured from each trainer's own location or from the nearest place they run sessions, services or classes. Results are sorted by distance; the API also returns where each distance was measured from.

Is the Trainr app on Google Play and the iPhone App Store?

The Android app (fit.trainr.app) is in production on Google Play and takes live Stripe payments. We took the iOS build through TestFlight, including the signing fix that had blocked uploads, and Trainr's iPhone app was released by Trainr's own team after our handover.

How was Trainr handed over to the client's new developer?

On 13 August 2026 we handed over the accounts, signing material, deploy and rollback runbooks, environment templates for all four repositories, a full export of the media bucket and our point-by-point response to the client's 27-item technical review, with a written record of what was still open and who owned it.

More work

Other things we shipped

All projects
Storafleet fleet overview with storage totals, trend line, transfers list and per-provider footprint barsFeatured
Saasfor Storafleet

Storafleet — Multi-Cloud Storage Control Plane

Storafleet is CountryDevs' own SaaS control plane for object storage. Teams connect buckets they already own on 50+ providers, including AWS S3, Cloudflare R2, Backblaze B2, Wasabi and MinIO, then browse, migrate, search, secure and cost-check them from one console.

Next.js 15ReactRedux ToolkitNode.js+11
50+Storage providers in the connection roster
Case study
Pakka Logistics home page hero on desktopFeatured
Mobile Appfor Pakka Logistics

Pakka Logistics — On-Demand Goods Delivery Platform

Pakka Logistics is an on-demand goods delivery platform for India. We built all of it: Android apps for customers and drivers, a business portal, an operations console and the website. The web platform is live and both Android apps are on Google Play; the two iPhone apps are in App Store review.

React NativeTypeScriptRedux ToolkitNode.js+12
96.3%API routes exercised by a probe before launch (260 of 270)
Case study
Superadmin stories grid with photo storiesFeatured
Mobile Appfor Slic Solutions Private Limited

LetSlic — Social Media App with Group Chat, Stories and Premium

LetSlic, listed on Google Play as Slic, is a social app for photo posts, 24-hour stories and real-time chat with group moderation. We built it for Slic Solutions Private Limited: the React Native app, two Node.js APIs, the superadmin console and the website, plus Premium and ads.

React NativeTypeScriptReduxNode.js+13
About 25Days of build work, across a project that ran Jun 2025 – Jul 2026 at the client's pace
Case study
Got a project?

Let's build the
next one together

Tell us what you're trying to ship and which project here is closest to it. We'll reply with the questions that shape the plan, then start with a two-week Discovery Sprint, so you see the screens and the plan before any code is written.

Not ready for a full build? Start with a Discovery Sprint, or get a code audit of an app someone else built. See how a project runs with us.

More on what we build for businesses in the UAE: App Development for UAE Businesses.