In plain terms

FitPulse is the software a gym chain runs its business on: who is a member, what they paid for, which branch they belong to, whether they came in today, and what the trainers are owed.

Several separate gym businesses use the same platform at the same time, each with their own branding, their own web address, and data none of the others can reach. Members walk up to a fingerprint reader the gym already owned and are through the door in a second. The owner opens a dashboard and sees today’s check-ins as they happen.

Why it matters commercially: the gym replaces a spreadsheet, a card machine and a lot of arguing about who came in when. And because every customer company runs on shared infrastructure, the platform can sign a new gym without buying anything new.

The problem

A gym chain is not one business. It is a head office, several branches, staff who belong to exactly one of them, trainers who float between two, and members who bought a plan at one location and expect it to work at another.

Model that badly and every question the software asks the database needs a “…and only for this branch” clause that someone will eventually forget. Forget it once and a receptionist in Dhanmondi is looking at Gulshan’s revenue - or worse, a rival gym’s. That is not a bug report; that is a lost contract.

Model it well and the separation is structural: impossible to forget, because it is not something anyone has to remember.

FitPulse is a platform for that shape of business, serving a web dashboard, a member phone app and biometric hardware from one system.

Keeping customers apart, in two dimensions

Between businesses: each gets its own database schema. Every customer company gets its own separate space inside a shared database cluster. The web address a visitor arrives on - including a gym’s own custom domain - decides which space is opened, for that request only. A missing filter cannot leak another company’s data because the other company’s tables are not visible at all.

For the business, this is the difference between “we are careful” and “it cannot happen”. It is also what makes custom-domain branding possible: a gym can put the product on their own web address without anyone building them a separate copy of the system.

Inside a business: branch scoping. Within one company, branch access is enforced deep in the data layer rather than screen by screen. Screens ask for “the members I am allowed to see”; the system decides what that means based on the person’s role and which branch they work at. A curious staff member editing the web address in their browser gets a refusal, not another branch’s payroll.

Permissions then resolve three ways at once: the subscription plan says which features exist, the role says which actions are allowed, and the record says whether it is in reach. Any one of them saying no means no. I wrote about the trade-offs of this approach in Schema-per-Tenant Django.

Talking to the turnstile

Attendance is the feature customers actually judge the product on, and it runs on ZKTeco fingerprint terminals sitting on a gym’s local network - not on the internet, and not reachable from a cloud service.

The bridge is a small program that runs on the gym’s own network, speaks to the device the only way the device understands, and forwards the results upstream. It stores events locally when the internet drops, avoids sending anything twice when the connection returns, and treats the device’s own clock as unreliable. Every record carries both the time the device claimed and the time the server received it - because those disagree more often than you would expect, and payroll depends on knowing which one was used.

That bridge is its own project - ZKTeco LAN Agent - and it exists for a commercial reason: the cheaper terminals customers already own cannot reach a cloud service at all, and telling a prospective client to re-equip every branch before they can start is not a sentence that closes a deal.

The attendance data then feeds a rules engine covering late arrivals, leave, overtime and payroll - rules that differ per branch, per role, and occasionally per employee. The outcome for the customer is that the argument about someone’s hours is settled by a record, not by memory.

Holding 10,000+ daily users without a bigger bill

Growth is only good news if serving it stays affordable. Scaling this platform was mostly about refusing to buy hardware first:

  • Connection pooling (PgBouncer). The application was exhausting its database connections long before it ran out of processing power - a queue forming at a door that was needlessly narrow. Pooling widened the door for free.
  • Caching (Redis). Dashboard figures that were being recalculated from scratch on every page view are now computed once and reused. Pages that felt slow became instant.
  • Fixing the queries before adding capacity. The usual culprits: reports asking the database a thousand small questions instead of one, and missing indexes on exactly the branch-and-date combination every report filters by.
  • Live updates instead of constant polling. The check-in feed pushes new arrivals to the screen rather than asking the server every five seconds whether anything has changed - less server load and a more responsive product at the same time.

The full breakdown is in Holding 10k Daily Users Before You Buy a Bigger Server.

Shipping without going offline

New versions start up and prove they are healthy before a single member is sent to them; the old version is retired only afterwards. Nobody sees a maintenance page, and nothing has to happen at 2am. Going back to the previous version is redeploying the previous release - deliberately boring, because rollback should never require thinking.

The result is cultural as much as technical: fixes and features reach customers in days instead of waiting for a safe window.

The result

  • 10,000+ people a day rely on it, and growth was absorbed by engineering work rather than a larger infrastructure bill.
  • No downtime during releases, so improvements reach customers continuously instead of in nervous batches.
  • A new gym can be onboarded onto the existing platform with its own branding and its own domain - no separate installation to maintain.
  • Existing biometric hardware works, which removed a capital cost from the buying decision.
  • Members get one consistent experience across the front desk, their phone and the door.

What it demonstrates

This is the project where “full stack” is literal: the data design, the backend, the permission model, the management dashboard, the member phone app, the hardware integration and the deployment pipeline underneath it all. Every one of those layers was shaped by the same constraint - a mistake made in one customer’s account must never become a problem in another’s.