Made2Fit's building in the night town from the home page, its billboard playing the app
All work

Made2Fit

A training and nutrition coach that shows why every number is there.

Built solo · 211 commits · 1,800+ automated tests · Source on GitHub

What I built, in one look

  1. Unsourced numbers are rejected
  2. Body photos cannot be stored
  3. Safety gates that hold on their own
  4. CI that checks data and coverage, not only code
  5. Backups that are tested
Read how each one works

How it fits together

  1. React Native appIntake, programme, progress
  2. Fastify APIZod contracts, JWT, rate limits
  3. Deterministic engineProgramme rules and safety gates
  4. AI gatewayClaude and Gemini, numbers checked against the trace
  5. PostgreSQLDrizzle migrations, no photo columns

What it does

Made2Fit turns a short intake (eight cards, 53 questions) and an optional body photo into a personal training and nutrition programme. Every exercise comes with the reason it was picked, a clear progression rule (“hit 12 reps on all sets, move to a harder variation next week”), and a note on which exercises were removed from the pool because of the injuries or limits you reported. If your injury, equipment or goal changes, you update your answers and the programme is recomputed.

Nutrition works the same way: log food by search, barcode or photo, and the same food always gives the same macros. A weekly meal plan with meal swaps and a shopping list is built around your budget, who cooks at home and a Ramadan mode that moves meal windows and training times. A chat coach answers questions using your own training history, food log and measurements, and sends health questions to a doctor instead of diagnosing.

Key features

  • Card-based intake built on PAR-Q+ and ACSM screening, with branching questions that only appear when an earlier answer makes them relevant.
  • Programme generation from a library of 122 exercises, with a written reason per exercise and per weekly structure (“All the decisions”).
  • Weekly progression and deload decisions driven by the user’s feedback, using fixed rules.
  • Four safety gates (age, pregnancy/breastfeeding, cardiac red flags, eating-disorder history) that block, pause or hide numbers instead of letting the user through.
  • Body-photo analysis that reports body fat as a range, not a single number, and never stores the photo.
  • Food logging by search, Open Food Facts barcode lookup or photo recognition; a deterministic weekly meal planner with swaps, a shopping list and a “my fridge” view.
  • Progress tracking by exercise load, daily weight and photo comparison; full data export and one-tap account deletion.
  • Turkish and English UI, free / Basic / Pro tiers through in-app subscriptions.

How it’s built

React Native appCoach chatFastify APISafety gatesDeterministic engineRule trace = "why"LLM: prose onlyNumber checkPostgreSQL + Drizzle product machinery

Made2Fit is a TypeScript monorepo with four workspaces: packages/shared (domain model, design tokens, Turkish/English copy, compiled question and exercise data), packages/core (the calculation engine), packages/api (Node.js + Fastify + PostgreSQL via Drizzle) and apps/mobile (React Native with Expo and expo-router). A static brand site in apps/site is served by the same Caddy origin as the API.

The core engine is a set of pure functions with no network calls, no file access and no Date.now(): anything time-dependent takes the reference date as an argument. The same profile always produces the same programme, which makes determinism something tests can check, and the same engine runs on the server and on the phone. It covers profile derivation, volume budgets per muscle group, exercise scoring and selection (programUret), progression and deloads, nutrition targets, the meal-plan constraint solver, the Ramadan calendar and the safety gates. Each decision carries a trace of the rules that produced it, and that trace is what the user sees as “why”.

The LLM is limited to writing prose. All model calls go through one gateway client with a per-task model table (packages/core/src/ai/gecit.ts), where model name and price sit on the same row so cost accounting cannot drift from the model actually used. sayilariDogrula() extracts every number from the model output and rejects the text if any number is missing from the source data; every AI path has a deterministic fallback, and the client times out at 20 seconds and falls back rather than waiting. Meal planning was deliberately kept out of the model entirely.

On the server, the four safety gates are re-evaluated from the merged answers on every answer submission, after each answer is validated against its question definition. AI usage quotas are reserved atomically to close a read-then-increment race. The production setup is Docker Compose with hardened containers (read-only filesystems, all capabilities dropped, no-new-privileges), Postgres not exposed, Caddy handling TLS and security headers, a nightly pg_dump container, a scripted restore drill, and a GitHub Actions monitor that checks the server every 30 minutes and emails only on failure.

Tech stack

Languages
  • TypeScript (strict, project references)
  • Node.js 20+/22
Backend
  • Fastify 5
  • @fastify/jwt
  • @fastify/helmet
  • @fastify/rate-limit
  • @fastify/cors
  • Zod
  • Pino
Database
  • PostgreSQL 17
  • Drizzle ORM and drizzle-kit migrations
  • PGlite (in-memory Postgres for tests)
Mobile
  • React Native 0.76
  • Expo SDK 52
  • expo-router
  • expo-camera
  • expo-notifications
  • expo-secure-store
  • react-native-svg
  • RevenueCat (react-native-purchases)
  • Google Mobile Ads
AI
  • Anthropic-format messages through an AI gateway; Claude Opus for intake interpretation and body analysis
  • Gemini Flash Lite for coach chat and food recognition
Data
  • Open Food Facts (barcodes)
  • compiled exercise and question banks with build-time validators
Testing and quality
  • Vitest 4 with V8 coverage
  • ESLint 9
  • typescript-eslint
  • Prettier
  • translation and dependency-audit scripts
Infrastructure
  • Docker Compose
  • Caddy 2
  • GitHub Actions (CI, release, uptime monitor)
  • EAS builds
  • App Store Connect and Google Play scripted via their APIs

Engineering highlights

  • Unsourced numbers are rejected. sayilariDogrula() compares every number in model output with the numbers in the source decision trace and drops the text on any mismatch, falling back to the deterministic explanation. packages/core/src/ai/gecit.ts
  • Body photos cannot be stored. A schema test queries information_schema and fails if any table gains a photo, image, blob or base64 column; food logs may keep only a photo_hash. packages/api/src/db/sema.test.ts
  • Safety gates that hold on their own. Age, pregnancy, cardiac and eating-disorder gates; a skipped mandatory screening question counts as unanswered, and the gates are re-evaluated on every answer save. packages/core/src/kapilar/kapilar.ts, packages/api/src/rotalar/degerlendirme.ts
  • CI that checks data and coverage, not only code. Exercise and question banks are recompiled in CI and git diff --exit-code fails the build if generated files are stale; engine coverage is gated at 85% lines/functions/statements and 80% branches. .github/workflows/ci.yml, vitest.config.ts
  • Backups that are tested. A nightly pg_dump sidecar, plus a restore drill that restores into a throwaway Postgres container and compares tables and row counts without touching the production database. infra/docker-compose.yml, scripts/yedek-geri-yukleme-testi.sh