Case 04Gym management

GymOS

Enrollment, billing and check-in for two gyms in a single system.

A management system I designed and built on my own for a neighborhood gym with two locations: member records, plans, recurring billing, check-in, classes, workouts and KPIs in one place. The front desk stops checking notebooks and spreadsheets, and late payments are handled the day they happen.

Client
Neighborhood gym in São José dos Campos, Brazil · 2 locations
Period
2026
Team
Solo project · from discovery to rollout
Role
Full stack engineer · custom build, end to end
Stack
Next.js · React · TypeScript · Tailwind CSS · NestJS · Prisma
Confidentiality
Client data kept private
02

Results in numbers

  • 500+active members managedtwo locations and 30 instructors in one system
  • 40%fewer late payments in six monthsrecurring billing with a retry schedule
  • 3 sto clear a member at the entrancepreviously a manual form and payment check
  • 12 hper week given back to the front deskno billing spreadsheet, no entry notebook
03

The business context

Client
Neighborhood gym in São José dos Campos, Brazil · 2 locations
Period
2026

The gym serves more than 500 members across two locations in the same neighborhood, with 30 instructors rotating shifts and classes. Until then, operations ran on paper forms, one spreadsheet per location and fees collected at the front desk. Staff checked the member’s name in a notebook, nobody knew for sure who was up to date, who could train at the other location or when a plan expired, and late payments only surfaced at month-end close, when they were already hard to recover.

04

The challenge

The owner did not want generic off-the-shelf software: he wanted the system to follow the house rules. That meant controlling access by plan and by location, charging every month without depending on someone’s memory, blocking and releasing members according to payment and giving instructors their schedule and student list without exposing the finances. All of it for a team that had never used any system before.

  1. Two locations, one operation

    Each location has its own hours, capacity and staff, but the member is one. Some plans are valid at one location and others at both, and each location’s manager should only see their own.

  2. Late payments found too late

    Without recurring billing, the monthly fee depended on the member remembering and the front desk writing it down. Members who fell behind kept training for weeks until someone noticed in the spreadsheet.

  3. The front desk as a bottleneck

    At peak hours a line formed at the entrance: find the form, check the payment, ask about the plan. Manual checks slowed members down and still let in people who should not get in.

05

What I delivered

I built GymOS as a monorepo with a NestJS API, Next.js dashboards and PostgreSQL through Prisma. Each person has their own entry point: front desk and management in the gym dashboard, instructors in the instructor portal, members in their own area with check-in, classes, workouts and progress. Behind it, a single access rule decides who gets into each location, and a recurring billing flow charges, retries, blocks and releases on its own.

  1. Recurring billing with dunning

    Monthly fee generated on the due date, up to 3 attempts at 0, 3 and 7 days and automatic blocking of unpaid members.

  2. Check-in with a single rule

    Status, plan, freeze, overdue bills, location and weekly limit checked in one call, from the member app or the front desk.

  3. Instructor portal and member area

    Instructors get schedule, classes and attendance; members get workouts, class bookings and body-measurement progress.

  4. Management dashboard and BI

    Recurring revenue, churn, average ticket, occupancy by hour, finance, point of sale and stock for both locations.

06

How it works

Follow the work through the system step by step, or click any component on the board to see what it does.

11 components · 12 connections · 5 layers

Click a component to see details←→to navigateWalk through the flow below

INPUTFront desk andmanagementNext.jsINPUTMember areaNext.jsINPUTInstructor portalNext.jsSECURITYGymOS APINestJS · JWTSERVICEAccess controlNestJSSERVICERecurring billingNestJSSERVICEClasses andworkoutsNestJSDATABASEGym databasePostgreSQL ·PrismaEXTERNALPayment gatewayAdaptador · StripeQUEUEQueues and realtimeRedis · BullMQ ·…OUTPUTEmailSMTP

drag to see the flow

Legend

  • Synchronous
  • Asynchronous
  • Secure channel
  • External integration
Text description of the flow
  1. From Front desk and management to GymOS API — Enrollment (synchronous).
  2. From Member area to GymOS API — Check-in and bookings (synchronous).
  3. From Instructor portal to GymOS API — Attendance (synchronous).
  4. From GymOS API to Access control — Validates entry (synchronous).
  5. From GymOS API to Recurring billing — Activates the plan (synchronous).
  6. From GymOS API to Classes and workouts — Bookings (synchronous).
  7. From Access control to Gym database — Records check-in (synchronous).
  8. From Recurring billing to Gym database — Subscription (synchronous).
  9. From Recurring billing to Queues and real time — Queues notice (asynchronous).
  10. From Recurring billing to Payment gateway — Charges monthly fee (external integration).
  11. From Classes and workouts to Gym database — Workouts (synchronous).
  12. From Queues and real time to Email — Retries (asynchronous).

Walk through the flow

←→change step

5 steps

From start to finish

Pick a step or press Play to follow the whole flow. The board highlights where the work goes as you move along.

07

System components

  1. Complete member record

    What it solves
    Replace the paper form with a record that follows the member from day one to renewal.
    How it works
    Enrollment brings together tax ID, contact, emergency contact, an address filled in from the postal code via ViaCEP, the PAR-Q questionnaire and health notes. Physical assessments store weight, body fat and measurements; attachments store photo and contract. Every status change is kept in history, and the member gets a link to activate their own account.

    NestJS · Prisma · PostgreSQL · ViaCEP

  2. Plans and subscriptions

    What it solves
    Turn the gym’s plans into rules the system enforces on its own.
    How it works
    Each plan defines price, monthly, quarterly or yearly cycle, enrollment fee, weekly access limit and whether it covers one location or both. A subscription can be frozen, cancelled with a reason or expired, generates the member’s contract and feeds the sign-ups versus cancellations report.

    NestJS · Prisma · Next.js

  3. Recurring billing and dunning

    What it solves
    Charge every month without relying on anyone’s memory, and act the same day a payment fails.
    How it works
    The billing process finds subscriptions coming due, creates the payment once per due date and makes up to three attempts, at 0, 3 and 7 days. Every attempt and event is recorded. Once attempts run out, the subscription goes past due and the member is suspended; when payment comes in, the next due date is calculated and access returns immediately.

    NestJS · Prisma · PostgreSQL

  4. Payment gateway behind an adapter

    What it solves
    Receive payment confirmations securely and never process the same event twice.
    How it works
    The gateway sits behind a single interface, with a Stripe adapter and a test adapter. Every webhook has its HMAC SHA-256 signature checked with a constant-time comparison, and the event ID is unique in the database: if the gateway resends, the duplicate is dropped with no side effects.

    NestJS · Stripe · HMAC

  5. Access control at each location

    What it solves
    Decide in seconds, and always the same way, whether a member can enter that location.
    How it works
    A single validation checks member status, active subscription, freeze, overdue payments and invoices, the location allowed by the plan and the weekly access limit, and blocks duplicate check-ins on the same day. The same rule serves the member app, the front desk and an integration endpoint ready for a turnstile.

    NestJS · Prisma · Next.js

  6. Classes, workouts and instructor portal

    What it solves
    Organize the routine of 30 instructors and put workouts and schedule on the member’s phone.
    How it works
    Classes have a location, instructor, day, time and capacity; bookings respect available spots and move through booked, checked in, present, no-show or cancelled. Instructors see their own schedule and students and mark attendance. Workouts bundle exercises with sets, reps, rest and video, and members track their measurements in their own area.

    Next.js · NestJS · Prisma

08

Decisions that hold the system up

  1. One database, two locations

    Instead of one system per location, every record carries the gym and the location, and each manager only sees their own. The member stays one person, the plan decides where they train and the owner sees both locations together. Opening a third location becomes a form, not a project.

  2. The access rule lives in one place

    Member app, front desk and turnstile all call the same validation. There is no "it works at the desk but not in the app": any rule change applies to every entry point at once.

  3. Late payments handled by the schedule, not by people

    I chose three attempts spaced at 0, 3 and 7 days, with automatic suspension at the end and automatic reactivation on payment. The front desk no longer has the awkward conversation at the counter, and members know exactly what happens if they fall behind.

  4. Gateway behind an adapter

    Billing talks to an interface, not to a vendor. Switching gateways or testing the whole flow without charging anyone is a matter of configuration, and signed, idempotent webhooks protect the books from resends and forged requests.

09

Business results

  • Late payments are no longer found at month-end: members who do not pay are notified, charged again at 3 and 7 days and blocked at the entrance until they settle.

  • Front-desk check-in dropped from about 1 minute to a few seconds, and the peak-hour line disappeared.

  • The owner follows recurring revenue, churn and occupancy for both locations in real time, without building spreadsheets.

  • All 30 instructors now see their schedule, classes and students in their own portal, and shift swaps left the group chat.

10

My role

Role
Full stack engineer · custom build, end to end
Team
Solo project · from discovery to rollout

I built GymOS on my own as a custom project for the client. I started by sitting down with the owner and the front desk to understand how the gym really worked: plans, exceptions, peak hours, what slowed the day down. From there I designed the architecture, modeled the data, built the API, the dashboards and the member area, put it in production with Docker, trained the staff at both locations and keep supporting and evolving the system with them.

What I owned

  • Ran discovery with the owner and the front desk and turned the house rules into requirements.
  • Modeled the domain in PostgreSQL with Prisma: members, plans, subscriptions, payments, invoices, check-ins, classes and workouts.
  • Built the modular NestJS API with JWT, five access roles and per-location scoping.
  • Implemented recurring billing, the dunning flow and the gateway’s signed webhooks.
  • Developed the Next.js dashboards for management, instructors and members, with Recharts KPIs.
  • Deployed with Docker, trained the staff at both locations and provide ongoing support.
11

Stack by layer

  • Interface

    • Next.js
    • React
    • TypeScript
    • Tailwind CSS
    • Recharts
  • Backend

    • NestJS
    • Node.js
    • Prisma
    • Zod
    • JWT
  • Data and queues

    • PostgreSQL
    • Redis
    • BullMQ
    • Socket.IO
  • Infra and quality

    • Docker
    • Docker Compose
    • Jest
    • Playwright
    • Swagger
12

What I take from this project

  1. In a custom project, discovery is half the product. The rules that mattered most, like plans valid at both locations or weekly access limits, only surfaced by talking to the front desk at peak hours.

  2. Rolling out also means training. Adoption at both locations came when each role got a screen designed for their day, not one dashboard with everything for everyone.

  3. Money calls for idempotency from day one. Treating repeated webhooks and duplicate charges as the rule, not the exception, kept every entry in the books single.

Next case 01 Steuer Invoices that arrive on their own and land reconciled in Linx.

What’s the next system your business needs?

Tell me the context: a problem, an idea or a system that needs to evolve. I’ll reply with a clear technical path and the first step to get it built.