A full-stack gym management application for member registration, front-desk scanning, staff attendance, payments, and WhatsApp broadcasts.
Stack: NestJS + SQLite (backend) · React + Vite + Tailwind CSS (frontend)
Repository: github.com/mohamedmoamen8/gym_system
| Module | Description |
|---|---|
| Dashboard | Member counts, today's check-ins, 7-day visit chart, revenue totals |
| Front Desk Scanner | Scan or type a member barcode to verify access and record check-ins |
| Register Member | Add members with photo, phone, barcode, and membership plan |
| Members | Search, filter, edit, and print member ID cards |
| Membership Plans | Create and manage subscription tiers with pricing and duration |
| Payments | Record payments, track revenue, and view monthly analytics |
| Staff | Manage staff profiles with photos and access codes |
| Captain Station | Staff clock-in / clock-out via access code with today's duty roster |
| Broadcast | Send WhatsApp messages to selected active members via OpenWA |
Additional capabilities:
- JWT authentication with forced password change on first login
- Configurable gym name and logo (settings)
- Automatic membership expiry (hourly background job)
- Member status: Active, Suspended, Expired
gym-management-system/
├── backend/ NestJS API — port 3000
│ ├── src/
│ │ ├── auth/ Login, JWT, owner account
│ │ ├── customers/ Member CRUD and barcode lookup
│ │ ├── checkins/ Visit tracking and analytics
│ │ ├── memberships/ Subscription plans
│ │ ├── payments/ Payment records and revenue
│ │ ├── staff/ Staff profiles and clock logs
│ │ ├── broadcast/ WhatsApp messaging (OpenWA)
│ │ ├── settings/ Gym branding and config
│ │ └── jobs/ Scheduled expiry updates
│ └── storage/photos/ Uploaded member and staff photos
└── frontend/ React + Vite — port 5173
└── src/components/ UI tabs and pages
- Node.js 18+
- npm
cd backend
npm install
cp .env.example .env # optional — edit JWT secret and OpenWA settings
npm run start:devThe API runs at http://localhost:3000/api.
Uploaded photos are served at http://localhost:3000/uploads/photos/<filename>.
On first run, a default owner account is created (see .env.example):
| Field | Default |
|---|---|
| Username | owner |
| Password | gym1234 |
Change the password after your first login.
cd frontend
npm install
npm run devOpen http://localhost:5173. The Vite dev server proxies /api and /uploads to the backend.
Copy backend/.env.example to backend/.env:
| Variable | Description |
|---|---|
PORT |
API port (default 3000) |
JWT_SECRET |
Secret for signing JWT tokens — change in production |
OWNER_USERNAME |
Default owner username seeded on first run |
OWNER_PASSWORD |
Default owner password seeded on first run |
OPENWA_URL |
OpenWA server URL (e.g. http://localhost:2785) |
OPENWA_API_KEY |
API key from the OpenWA dashboard |
OPENWA_SESSION |
OpenWA session name (e.g. gym) |
Leave OpenWA variables empty to run broadcasts in mock mode (logs only, no messages sent).
All routes are prefixed with /api. Most endpoints require a Authorization: Bearer <token> header.
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/auth/login |
No | Login with username and password |
PATCH |
/auth/change-password |
Yes | Change the logged-in owner's password |
| Method | Route | Description |
|---|---|---|
POST |
/customers/register |
Register a member (multipart, optional photo) |
GET |
/customers |
List members (?q= search, ?status= filter) |
GET |
/customers/next-barcode |
Next auto-generated member ID |
GET |
/customers/barcode/:code |
Lookup by barcode (records check-in) |
GET |
/customers/expiring |
Members expiring within N days (?days=7) |
GET |
/customers/:id |
Get member by UUID |
PATCH |
/customers/:id |
Update member |
DELETE |
/customers/:id |
Remove member |
| Method | Route | Description |
|---|---|---|
POST |
/checkins |
Record a check-in { "customerId": "..." } |
GET |
/checkins/today |
Today's check-in count |
GET |
/checkins/daily |
Daily counts for last N days (?days=7) |
GET |
/checkins/member/:customerId |
Check-in history for a member |
| Method | Route | Auth | Description |
|---|---|---|---|
GET |
/memberships/active |
No | Active plans (used by register form) |
GET |
/memberships |
Yes | All plans |
POST |
/memberships |
Yes | Create plan |
PATCH |
/memberships/:id |
Yes | Update plan |
DELETE |
/memberships/:id |
Yes | Delete plan |
| Method | Route | Description |
|---|---|---|
POST |
/payments |
Record a payment |
GET |
/payments |
List all payments |
GET |
/payments/customer/:customerId |
Payments for a member |
PATCH |
/payments/:id/status |
Update payment status |
GET |
/payments/analytics/revenue |
Total revenue |
GET |
/payments/analytics/monthly |
Monthly revenue breakdown |
| Method | Route | Auth | Description |
|---|---|---|---|
POST |
/staff |
Yes | Create staff member (multipart, optional photo) |
GET |
/staff |
Yes | List all staff |
GET |
/staff/logs/today |
No | Today's shift logs |
GET |
/staff/:id |
Yes | Get staff by UUID |
PATCH |
/staff/:id |
Yes | Update staff |
DELETE |
/staff/:id |
Yes | Remove staff |
POST |
/staff/clock-trigger |
No | Clock in/out { "accessCode": "..." } |
| Method | Route | Description |
|---|---|---|
POST |
/broadcast |
Send message to member UUIDs { "message": "...", "recipientIds": [...] } |
| Method | Route | Description |
|---|---|---|
GET |
/settings |
List all settings |
GET |
/settings/:key |
Get a setting value |
POST |
/settings/:key |
Set a setting { "value": "..." } |
To send real WhatsApp messages:
- Run OpenWA locally (default port
2785) - Create a session in the OpenWA dashboard and scan the QR code
- Create an API key under Admin → API Keys
- Set
OPENWA_URL,OPENWA_API_KEY, andOPENWA_SESSIONinbackend/.env
Phone numbers should be international digits only (e.g. 201012345678).
- Database: SQLite (
backend/gym.sqlite) works for single-machine deployments. For multi-server setups, switch to PostgreSQL via@nestjs/typeorm. - Migrations: Set
synchronize: falseinapp.module.tsand use TypeORM migrations for production. - Photos: Stored locally in
backend/storage/photos/. Consider S3 or similar for production. - Security: Change
JWT_SECRET, default owner credentials, and restrict CORS origins inmain.ts. - Build frontend:
cd frontend && npm run build— serve thedist/folder behind your reverse proxy.
Private — UNLICENSED