Membership, scheduling, billing and equipment management for gym owners — with a member app your clients actually use.
Built with Next.js 15 (App Router), MongoDB and Tailwind CSS.
Business dashboard
- Members — full records, membership plans, renewal dates, freezing, notes, emergency contacts, search, filtering and pagination.
- Front-desk check-in — searchable member picker, one check-in per day, feeding the daily count and the peak-hours report.
- Schedule — group classes and personal training, day and week views, capacity with an automatic waitlist, attendance marking, and bulk creation of weekly repeats.
- Payments — record, edit, refund and export revenue. A completed membership payment rolls the member's paid-through date forward by one billing cycle.
- Trainers — rosters, specialisms, upcoming sessions and client lists.
- Inventory — equipment, servicing schedules, reorder levels and asset value.
- Analytics — revenue trend, member growth, peak hours, revenue by service and trainer attendance, all computed from live data.
- Settings — gym profile (currency, time zone, locale), membership plans, team accounts and notification preferences.
Member app (/member)
Membership status and renewal, class booking with waitlists, progress tracking with weight/body-fat charts and goals, billing history, and self-service profile and password management.
Roles
| Role | Access |
|---|---|
| Owner / Admin | Everything, including settings, team management and deleting payments |
| Front desk | Members, schedule, payments, inventory, check-ins. Read-only settings |
| Trainer | Schedule, attendance and check-ins, plus read-only member details |
| Member | Member app only |
Dashboard homepage
Sign in
Members
Schedule
Payments
Inventory
Analytics
Trainers
Settings
Member app — overview
Member app — classes
Member app — progress
Member app — payments
- Node.js 20+
- MongoDB 6+ (local, Docker or Atlas)
Clone the repository and install dependencies:
git clone https://github.com/gagan-baghel/gym-manager.git
cd gym-manager
npm installCopy the environment template and fill in the values:
cp .env.example .env.localMONGODB_URI— your MongoDB connection string.JWT_SECRET— at least 32 random characters. Generate one withopenssl rand -base64 48. The app refuses to start in production without it.
No MongoDB handy? Start one with Docker:
docker run -d --name gym-mongo -p 27017:27017 mongo:7Then run the app:
npm run devOpen http://localhost:3000. The first visit to
/signup creates the owner account. After that, staff accounts are created
from Settings → Team, and members activate their own app login with the
email address the gym has on file.
npm run seed -- --resetCreates a fully populated gym — 48 members, 4 trainers, a two-month class
timetable with bookings and attendance, a year of payments, check-ins, progress
measurements and equipment — plus these logins (all Password123):
| Role | |
|---|---|
| Owner | owner@irontemple.example |
| Front desk | frontdesk@irontemple.example |
| Trainer | trainer@irontemple.example |
| Member | member@example.com |
Run npm run seed without --reset to leave existing data untouched.
gym-management/
├── app/ # Next.js App Router
│ ├── actions/ # Server actions (auth, members, payments, ...)
│ ├── api/auth/ # Session and logout route handlers
│ ├── dashboard/ # Staff app: members, schedule, payments, ...
│ │ ├── analytics/
│ │ ├── inventory/
│ │ ├── members/[id]/
│ │ ├── payments/
│ │ ├── schedule/[id]/
│ │ ├── settings/
│ │ └── trainers/[id]/
│ ├── member/ # Member-facing portal
│ │ ├── (portal)/ # Overview, schedule, progress, payments, profile
│ │ ├── login/, signup/, unlinked/
│ ├── login/, signup/ # Staff auth
│ ├── favicon.ico, icon.png, icon.svg, apple-icon.png
│ ├── layout.tsx, error.tsx, not-found.tsx
│ └── page.tsx # Redirects by role
├── components/
│ ├── app/ # App-specific building blocks (forms, dialogs, nav)
│ └── ui/ # Design-system primitives (buttons, dialogs, ...)
├── lib/ # Auth, session, MongoDB, queries, formatting
├── models/ # Mongoose schemas
├── scripts/ # seed.ts, smoke.mts, test.mts
├── public/ # Static assets, icons and screenshots
├── middleware.ts # Role-based route protection
└── next.config.mjs
npm run typecheck && npm run lint && npm testnpm test runs against a throwaway <your database>_test database and covers
time-zone parsing, money rounding, capacity and waitlist handling, membership
renewal maths, permissions and CSV escaping.
With a server running, npm run smoke signs in as each role over HTTP and walks
every route, asserting access control, redirects and that pages render live data:
npm run smokeUse BASE_URL=http://localhost:3001 npm run smoke to target a different port.
Build and start a production server:
npm run build
npm startDeploy anywhere that runs Node.js (Vercel, Railway, Fly.io, a VPS, or a container). Set the following environment variables in the target environment:
MONGODB_URIJWT_SECRET
Database indexes are created automatically on first connection — no migration step is required.
Vercel
- Import the repository into Vercel.
- Add
MONGODB_URIandJWT_SECRETunder Project Settings → Environment Variables. - Deploy. Vercel runs
npm run buildautomatically.
Docker / self-hosted
npm ci
npm run build
NODE_ENV=production MONGODB_URI=... JWT_SECRET=... npm startThe app listens on port 3000 by default; put it behind a reverse proxy (Nginx, Caddy, or your platform's load balancer) for TLS.
- Money is stored as integer minor units (cents) and only converted for display, so no rounding drift.
- Times are stored as UTC and rendered in the gym's configured time zone. Wall-clock input from forms is interpreted in that zone, including across daylight-saving boundaries.
- Sessions are JWTs in an httpOnly cookie. Middleware verifies the signature and routes by role before any HTML is produced; every page then re-checks the live account, so a deactivated user or a stale token is rejected even with a valid cookie. Changing a password invalidates all other sessions.
- Sign-in is rate limited per IP and per account, and returns the same message for an unknown email and a wrong password.
- Deletes are refused where they would destroy financial history: a member with payments must be deactivated instead, as must a trainer with upcoming sessions or a plan with members on it.