A gamified fitness tracking web application that turns your workouts and runs into an RPG progression experience. Earn XP, level up, complete quests, unlock achievements, and maintain your daily activity streak.
- Features
- Tech Stack
- Architecture
- Project Structure
- Getting Started
- Environment Variables
- Database Setup
- Available Scripts
- Testing
- Workout Logging — Log gym sessions with exercises, sets, reps, weight, and duration.
- Run Tracking — Log runs with distance, duration, pace auto-calculation, and difficulty rating.
- XP & Levelling System — Earn XP for every activity. Level up as your XP accumulates (each level requires
level × 500 XP). - Activity Streaks — Consecutive daily activity builds and maintains a streak counter.
- Quests — Daily, weekly, and special quests tied to workout and run metrics (count, distance).
- Achievements — Milestone-based badges unlocked by total workouts, distance, level, and streak.
- Dashboard — At-a-glance stats including weekly trends, lifetime XP, and active day tracker.
- Custom Exercises — Create and save your own exercises to use across workouts.
- Authentication — Email/password sign-up and sign-in via Better Auth.
- Stamina System — Stamina regeneration mechanic tracked per user.
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| Language | TypeScript 5 |
| Database | MongoDB 7 (Atlas or local Docker) |
| Auth | Better Auth |
| Data Fetching (Client) | TanStack Query v5 |
| Styling | Tailwind CSS v4 |
| Schema Validation | Zod v4 |
| Env Validation | @t3-oss/env-nextjs |
| Unit Tests | Vitest + mongodb-memory-server |
| E2E Tests | Playwright |
| Icons | Lucide React |
The codebase follows a Clean Architecture / Application Service pattern with strict separation of concerns across three backend layers:
┌─────────────────────────────────────────────┐
│ Next.js API Routes │ ← Thin adapters: parse request, call service
├─────────────────────────────────────────────┤
│ Services Layer │ ← Use-case orchestration (e.g. logWorkout)
│ src/lib/services/{domain}/ │ Validates → Calculates → Persists → Side-effects
├─────────────────────────────────────────────┤
│ Domain Layer │ ← Pure business rules (zero infrastructure deps)
│ src/lib/domain/*-rules.ts │ XP formulas, streak logic, quest rules, etc.
├─────────────────────────────────────────────┤
│ Data Layer │ ← MongoDB CRUD only, no business logic
│ src/lib/data/*-db.ts │ Maps DB documents ↔ TypeScript domain types
└─────────────────────────────────────────────┘
Key architectural decisions:
- Domain rules are pure functions — testable without a database or any side effects.
-db.tsfiles only persist — they do not calculate XP, evaluate quests, or call other modules.- Services orchestrate — the service layer calls domain → data → side effects in sequence.
- API routes are thin — they extract
userIdfrom the session, call the service, and return the response. - Singleton MongoDB client — uses a
globalvariable in development to survive hot-reloads. - Edge middleware auth —
src/proxy.tsprotects routes at the Next.js middleware level.
src/
├── app/
│ ├── (app)/ # Protected app pages
│ │ ├── dashboard/
│ │ ├── workouts/
│ │ ├── runs/
│ │ ├── quests/
│ │ └── profile/
│ ├── (auth)/ # Public auth pages (login, signup)
│ ├── (landing)/ # Public marketing/landing page
│ └── api/ # API route handlers
│ ├── workouts/
│ ├── runs/
│ ├── quests/
│ ├── achievements/
│ ├── exercises/
│ ├── profile/
│ ├── stats/
│ ├── user/
│ └── auth/ # Better Auth handler
├── components/ # Shared React components
├── lib/
│ ├── data/ # MongoDB access layer (*-db.ts)
│ │ └── api-client/ # Client-side fetch wrappers (used by TanStack Query)
│ ├── domain/ # Pure business rules (*-rules.ts)
│ ├── services/ # Use-case orchestration
│ │ ├── workouts/
│ │ ├── runs/
│ │ ├── quests/
│ │ ├── achievements/
│ │ ├── exercises/
│ │ └── users/
│ ├── auth/ # Better Auth server config & helpers
│ ├── context/ # React context providers
│ ├── hooks/ # Custom React hooks
│ ├── utils/ # Pure utility functions (formatters, calculations, dates)
│ ├── validations/ # Zod schemas for API input validation
│ ├── types.ts # Shared TypeScript domain types
│ └── mongodb.ts # MongoDB singleton client
├── scripts/ # One-off scripts (DB seeding, migrations)
├── env.ts # Type-safe environment variable validation
└── proxy.ts # Next.js edge middleware (route protection)
- Node.js
>=18(v20+ recommended) - npm
>=9 - A MongoDB instance — either MongoDB Atlas (cloud) or Docker (local)
git clone <repo-url>
cd fit_level_upnpm installCopy the example below into a .env.local file in the project root:
# .env.local
# MongoDB — Atlas connection string OR local (see Database Setup below)
MONGODB_URI="mongodb+srv://<user>:<password>@<cluster>.mongodb.net/?appName=<AppName>"
# OR for local Docker:
# MONGODB_URI="mongodb://localhost:27017"
# The name of the MongoDB database to use
MONGODB_DB="fitlevelup"
# The public URL of this app (used by Better Auth)
NEXT_PUBLIC_BETTER_AUTH_URL="http://localhost:3000"All collection names have sensible defaults (
workouts,runs,user_quests, etc.) and do not need to be set unless you want to override them.
Run the achievement seed script to populate the initial achievements:
npm run seednpm run devOpen http://localhost:3000 in your browser.
| Variable | Required | Default | Description |
|---|---|---|---|
MONGODB_URI |
Yes | — | Full MongoDB connection string |
MONGODB_DB |
Yes | — | Database name |
NEXT_PUBLIC_BETTER_AUTH_URL |
Yes | — | App base URL for Better Auth |
MONGODB_WORKOUTS_COLLECTION |
No | workouts |
Collection name override |
MONGODB_RUNS_COLLECTION |
No | runs |
Collection name override |
MONGODB_QUEST_TEMPLATES_COLLECTION |
No | quest_templates |
Collection name override |
MONGODB_USER_QUESTS_COLLECTION |
No | user_quests |
Collection name override |
MONGODB_ACHIEVEMENTS_COLLECTION |
No | achievements |
Collection name override |
MONGODB_USER_ACHIEVEMENTS_COLLECTION |
No | user_achievements |
Collection name override |
MONGODB_CUSTOM_EXERCISES_COLLECTION |
No | customExercises |
Collection name override |
All variables are validated at startup by src/env.ts using @t3-oss/env-nextjs + Zod. The server will refuse to start if any required variable is missing or malformed.
- Create a free cluster at mongodb.com/atlas.
- Create a database user and whitelist your IP.
- Copy the connection string into
MONGODB_URIin.env.local. - Run
npm run seedto populate achievement data.
The project ships with a docker-compose.yml that runs MongoDB 7 as a Replica Set (required for multi-document transactions).
# Start MongoDB in the background
docker compose up -d
# Verify the container is healthy
docker compose psSet your MONGODB_URI to:
MONGODB_URI="mongodb://localhost:27017"
Note: The Docker setup uses a Replica Set (
rs0) to support MongoDB transactions. Thehealthcheckindocker-compose.ymlautomatically initialises the replica set on first start.
| Script | Command | Description |
|---|---|---|
| Dev server | npm run dev |
Starts Next.js with Turbopack on http://localhost:3000 |
| Production build | npm run build |
Runs DB migrations then builds the Next.js production bundle |
| Production server | npm run start |
Starts the production Next.js server |
| Lint | npm run lint |
Runs ESLint across the project |
| Seed DB | npm run seed |
Seeds the achievements collection with initial data |
| Unit tests | npm test |
Runs Vitest unit tests (with in-memory MongoDB) |
| E2E tests | npm run test:e2e |
Runs Playwright end-to-end tests against localhost:3000 |
Unit tests live alongside the code they test in __tests__/ subdirectories. They use mongodb-memory-server to run a real (in-memory) MongoDB instance — no external DB required.
npm test
# Or with the interactive Vitest UI:
npx vitest --uiTests cover the domain rules (pure functions) and data layer operations.
End-to-end tests live in tests/e2e/. Playwright tests run against a live dev server on localhost:3000.
# Ensure the dev server is running first (or Playwright will start it automatically)
npm run test:e2e
# View the HTML test report
npx playwright show-report