Full-stack gym management system: marketing landing page + member portal + admin portal, backed by a JWT-secured Node/Express API and MySQL.
RyStarGym is a single-page web app for a fictional gym, built to practice end-to-end product development: a public landing page, a member-facing portal, and an admin back-office, all sharing one MySQL database and a role-based JWT auth system (separate secrets for admin and user sessions).
It covers real business logic beyond basic CRUD — membership expiry enforcement, prorated upgrades/downgrades, weekly class-capacity resets, daily check-ins, and PDF receipt generation — rather than just a static demo.
Landing Page
- Hero, programs, pricing, testimonials, and call-to-action sections.
Member Portal
- Register/login with JWT auth (password hashed with bcrypt).
- Purchase or extend membership (multi-month, extends from existing expiry if still active).
- Upgrade/downgrade plans with automatic proration (pay the difference, or switch free if downgrading).
- Book classes with weekly limit + per-class weekly capacity enforcement.
- Daily check-in (max once per day, requires active membership).
- Download payment receipts as PDF.
- Update profile.
Admin Portal
- Dashboard overview: stats widgets, recent check-ins, class utilization.
- Member database management.
- Membership plans & class schedule CRUD.
- Testimonials CRUD.
- Transaction history with search + in-browser receipt viewer.
- Per-class booking roster.
Backend Logic
- Membership expiry enforced on every booking/check-in request; expired memberships auto-cleared.
- Weekly scheduler resets
class_bookingsevery Sunday to free up capacity. - Dummy payment flow generates a row in
payments+ a PDF receipt on disk.
| Layer | Stack |
|---|---|
| Frontend | React 19, React Router 6, Tailwind CSS 4, Vite 7 |
| Backend | Node.js, Express 4, JWT (jsonwebtoken), bcryptjs |
| Database | MySQL (mysql2) |
| File handling | multer (uploads), pdfkit (receipt PDFs) |
| Tooling | ESLint, dotenv, morgan |
GymMembership/
├── backend/
│ ├── config/ # db, env, server bootstrap
│ ├── controllers/ # request handlers (admin, member, booking, payment, ...)
│ ├── middleware/ # JWT auth (admin + user)
│ ├── models/ # data access layer (one per entity)
│ ├── routes/ # Express routers
│ ├── sql/ # schema + seed + migrations
│ ├── receipts/ # generated PDF receipts
│ └── uploads/ # user-uploaded files
├── server/
│ └── index.js # backend entrypoint
├── src/
│ ├── components/
│ │ ├── home/ # landing page sections
│ │ ├── user/ # member portal
│ │ ├── admin/ # admin portal
│ │ └── layout/ # header/footer
│ ├── config/api.js # frontend API client config
│ └── App.jsx
└── vite.config.js
[users] ──current membership──▶ [memberships] ──payments/history──▶ [payments]
│ id, name, email, password, id, name,
│ membership_id (FK), expires_at, weekly_class_limit,
│ streak monthly_price
│
├──▶ [user_membership_history] (user_id, membership_id, selected_at)
│
└──▶ [class_bookings] ──▶ [class_sessions]
(user_id, class_session_id, (class_name, day_of_week,
status, booked_at) start/end_time, coach,
capacity, location)
Other supporting tables: dashboard_* (admin overview widgets), testimonials, homepage_content.
- Node.js 18+
- MySQL 8+
npm installcp .env.example .env.local| Variable | Description |
|---|---|
VITE_API_BASE_URL |
Frontend → backend API base URL |
PORT |
Backend port (default 4000) |
CLIENT_ORIGIN |
Allowed CORS origin (frontend dev URL) |
ADMIN_USERNAME / ADMIN_PASSWORD |
Seeded admin login |
ADMIN_JWT_SECRET / USER_JWT_SECRET |
Separate JWT signing secrets per role |
DB_HOST / DB_PORT / DB_USER / DB_PASSWORD / DB_NAME |
MySQL connection |
UPLOAD_DIR |
Where uploaded files are stored |
mysql -u root -p data_rystargym < backend/sql/schema.sql
mysql -u root -p data_rystargym < backend/sql/migrations/001_add_admin_username.sql
mysql -u root -p data_rystargym < backend/sql/seed_members.sql
mysql -u root -p data_rystargym < backend/sql/seed_dashboard.sql
mysql -u root -p data_rystargym < backend/sql/seed_home.sql
mysql -u root -p data_rystargym < backend/sql/seed_admin.sqlseed_members.sql adds 5 sample active members. seed_dashboard.sql fills admin overview widgets, seed_home.sql seeds landing page content, and seed_admin.sql creates the demo admin account below.
npm run serverAPI base: http://localhost:4000/api
npm run dev| Flow | Behavior |
|---|---|
| Activate/extend membership | POST /users/membership (no payment) or POST /payments/dummy (dummy pay + receipt). Supports multi-month; extends from current expiry if still active. |
| Upgrade/downgrade | Cheaper/equal plan → switches immediately, expiry unchanged. Pricier plan → charges the difference for the remaining period, expiry unchanged. |
| Expiry enforcement | Booking/check-in rejected once membership_expires_at has passed; membership auto-cleared on next profile/booking/check-in access. |
| Class booking | Checked against weekly_class_limit (0 = unlimited) and the class's weekly capacity. class_bookings is wiped every Sunday to reset capacity. |
| Check-in | One check-in per user per day, requires an active membership. |
| Payments | Dummy payment creates a payments row + PDF receipt in backend/receipts; admin can view it via GET /admin/payments/:id/receipt. |
| Field | Value |
|---|---|
| Username | admin@rystar.id |
| Password | admin123 |
JWT is stored in sessionStorage; sessions persist across refresh until the token expires or the user logs out.
Licensed under the MIT License.
Ryan Aric Ardhani
