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
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_schemaand fails if any table gains a photo, image, blob or base64 column; food logs may keep only aphoto_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-codefails 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_dumpsidecar, 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
