Skip to content

Mariomin23/TrainMeHard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

82 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

TrainMeHard

Full-stack wellness professional marketplace. Users browse and book certified trainers, nutritionists, and physiotherapists. Payments split automatically 50/50 between platform and professional via Stripe Connect.

Live demo β†’ trainmehard.vercel.app

TrainMeHard β€” homepage

TypeScript Next.js React Express MongoDB Stripe Tailwind Zod


What it does

TrainMeHard is a "one-shot" marketplace β€” users can browse profiles for free and unlock access to a professional by paying a single Initial Session fee. The platform takes 50% as commission; the professional receives the other 50% instantly via Stripe Connect's automatic transfer.

Three professional verticals share the same infrastructure, differentiated by professionalType:

Vertical Description
Fitness trainers In-person or online sessions
Nutritionists Meal planning and dietary consultations
Physiotherapists Physical assessment and rehabilitation

Architecture

TrainMeHard/
β”œβ”€β”€ fitness-frontend/   # Next.js 15 β€” App Router, TypeScript
└── fitness-backend/    # Node.js + Express 5 β€” REST API, TypeScript

The backend follows a strict layered pattern β€” nothing bleeds between layers:

Request β†’ Route (validation) β†’ Controller (orchestration) β†’ Service (business logic) β†’ Model (data)

The Stripe integration lives exclusively in payment.service and payout.service. Controllers never touch the Stripe SDK directly.


Tech stack

Layer Technologies
Frontend Next.js 15 Β· React 19 Β· TypeScript Β· Tailwind CSS Β· shadcn/ui Β· Zustand Β· React Hook Form Β· Axios
Backend Node.js 20 Β· Express 5 Β· TypeScript Β· MongoDB (Mongoose) Β· Zod Β· Stripe Connect SDK
Auth JWT (access + refresh) Β· httpOnly cookies Β· refresh token rotation
Security Helmet Β· express-rate-limit Β· mongo-sanitize Β· CORS whitelist Β· Zod (backend re-validation)
Logging Winston (structured) Β· Morgan (HTTP)
Testing Vitest Β· Supertest
Deploy Vercel (frontend) Β· Render (API) Β· MongoDB Atlas

Key features

Payments β€” Stripe Connect

The most complex piece. When a user pays for a session:

User triggers checkout
        β”‚
        β–Ό
PaymentIntent created in backend (amount determined server-side only)
        β”‚
        β–Ό
Stripe processes charge
        β”‚
        β”œβ”€β”€β–Ί 50% transferred to professional's Connect account (automatic)
        └──► 50% retained as application_fee_amount (platform)
        β”‚
        β–Ό
Stripe webhook confirms payment (idempotent)
        β”‚
        β–Ό
Session marked as `paid` in MongoDB β€” stripePaymentIntentId stored for audit

The webhook is the source of truth β€” the browser redirect is never trusted.

Authentication

  • Short-lived access tokens (15 min) in Authorization header
  • Long-lived refresh tokens (7 days) in httpOnly; Secure; SameSite=Strict cookie
  • Refresh token rotation on every renewal β€” previous token invalidated immediately (stolen token detection)

RBAC β€” 4 roles

Role Capabilities
super_admin Full access, admin management
admin Moderation panel, dispute resolution, metrics
professional Profile CRUD, availability, payout tracking
user Search, book, pay, review

Role is embedded in the JWT payload. A requireRole(...roles) middleware composes on routes:

router.delete('/users/:id', requireAuth, requireRole('admin', 'super_admin'), deleteUser);

Professionals require admin approval

After onboarding, a professional's profile is isApproved: false. An admin reviews and approves before the profile is publicly discoverable. Prevents fraudulent listings.

Dashboards

Three separate dashboard experiences:

  • User β€” session history, booking status, reviews left
  • Professional β€” profile management, availability calendar, payout history, onboarding wizard
  • Admin β€” professional approval queue, dispute resolution, platform metrics

Security hardening

Measure Tool Detail
HTTP headers helmet XSS, clickjacking, MIME sniffing protection
Rate limiting express-rate-limit 100 req / 15 min per IP on public routes
NoSQL injection mongo-sanitize + zod Input sanitization before any DB write
CORS cors Explicit origin whitelist only
Data validation zod Backend always re-validates β€” never trusts client
Prices Backend only amount is calculated server-side; front end never sends a price
Error exposure errorHandler middleware Stack traces never reach the client in production

Design decisions

Decision Why
Stateless JWT over server sessions Future mobile app compatibility
Stripe Connect over manual transfers Automatic split, regulatory compliance, no payout logic to maintain
MongoDB over PostgreSQL Flexible schema across 3 professional verticals with different field sets
Zustand over Redux 80% less boilerplate for this project scope
Shared Zod schemas (front + back) Single source of truth β€” if the schema changes, both sides break at compile time
Express 5 over Fastify Better ecosystem fit; native async/await error propagation in v5

Standardized API responses

Every endpoint returns the same envelope β€” no guessing the shape:

// Success
{ "success": true, "data": { ... } }

// Error
{ "success": false, "error": { "code": "PROFESSIONAL_NOT_FOUND", "message": "...", "statusCode": 404 } }

Error codes in SCREAMING_SNAKE_CASE for machine-readable handling.


Run locally

Backend

cd fitness-backend
npm install
cp .env.example .env   # fill in MONGODB_URI, JWT secrets, STRIPE keys
npm run dev            # http://localhost:3001

Frontend

cd fitness-frontend
npm install
npm run dev            # http://localhost:3000

Run tests

cd fitness-backend && npm test
cd fitness-frontend && npm test

Environment variables

# App
NODE_ENV=development
PORT=3001
FRONTEND_URL=http://localhost:3000

# MongoDB
MONGODB_URI=mongodb+srv://...

# JWT
JWT_ACCESS_SECRET=
JWT_REFRESH_SECRET=
JWT_ACCESS_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d

# Stripe
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=
STRIPE_PUBLISHABLE_KEY=

# Optional
CLOUDINARY_URL=
RESEND_API_KEY=
SENTRY_DSN=

License

MIT

About

πŸ‹οΈ Personal trainer marketplace β€” search, compare and book certified trainers. Next.js 15 + Express + MongoDB + Stripe. TypeScript monorepo.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors