Skip to Content
Living documentation — last reviewed 2026-05-28
ArchitectureArchitecture overview

Architecture overview

A bird’s-eye view of how FitKit is structured. Each section below either has a dedicated deep-dive (api.md, web.md, database.md, etc.) or links into the code.

Monorepo layout

Nx workspace, pnpm package manager. Two distinct kinds of project: applications (apps/) and libraries (libs/).

fitkit/ ├── apps/ │ ├── api/ NestJS backend (port 3001) │ ├── web/ Next.js 16 customer + dashboard app (port 3000) │ ├── admin/ Vite + React internal-only admin console │ ├── marketing/ Astro marketing site (separate Railway deploy) │ ├── minisites/ Astro per-org public minisite renderer │ └── docs/ Nextra-rendered docs site (this tree, served on port 3030) ├── libs/ │ ├── db/ @fitkit/db — Drizzle schema + client │ └── shared/ @fitkit/shared — Zod schemas + TS types shared API↔web↔mobile ├── docs/ this tree (canonical markdown source for apps/docs) └── scripts/ dev/test scripts, enrichment one-offs

The native member app (fitkit-mobile) lives in a separate repo sibling on disk (~/dev/fitkit-mobile/), not under apps/. It consumes @fitkit/shared as a versioned GitHub Packages dependency. See mobile.md for the rationale (toolchain divergence, EAS pipeline, native artifact churn).

Apps at a glance

AppPortPurposeStackDeploy
apps/api3001All business logic. REST + Socket.IO. Swagger at /api/docs.NestJS 11, webpack-built, Node 22Railway (apps/api/railway.toml)
apps/web3000Operator dashboard + member surfaces.Next.js 16, React 19, Tailwind v4, shadcn/uiVercel (vercel.json)
apps/admin(Vite dev)FitKit-internal admin tools.Vite + React + ClerkVercel (internal)
apps/marketingn/aPublic marketing site.AstroRailway (apps/marketing/railway.toml)
apps/minisitesn/aPer-org public minisites.AstroVercel; Vercel Domain API for custom domains
apps/docs3030This docs site.Nextra v4, Next.js 16GitHub Pages (.github/workflows/deploy-docs.yml)
fitkit-mobileNative member app (iOS + Android).Expo SDK 55, React Native 0.81, NativeWind 4, Clerk ExpoEAS Build + Submit (separate repo)

Libs at a glance

LibPath aliasPurpose
libs/db@fitkit/dbDrizzle ORM schema and createDbClient. All tables, enums, and types.
libs/shared@fitkit/sharedZod schemas + TS types shared between API, web, and mobile (response shapes, entity DTOs, platform-tier feature map, locale dictionaries). Mobile consumes it as the published @desmotech/fitkit-shared package on GitHub Packages.

Path aliases come from tsconfig.base.json.


Request flow (clients → API)

The API is the single source of business truth. Three first-class clients today; more planned.

Browser fitkit-mobile (iOS/Android) │ │ ▼ HTTPS ▼ HTTPS Next.js (Vercel) Expo Router app │ • Middleware │ • Clerk Expo session │ (apps/web/src/ │ (token cache in │ middleware.ts): │ expo-secure-store) │ - locale detection │ • useApi() hook attaches │ - Clerk auth on │ Clerk bearer + X-Locale │ protected routes │ • 10s JWT cache + single │ - legacy dashboard │ 401 retry │ path redirects │ │ Server components: │ │ - serverFetch(path) │ │ attaches bearer │ │ Client components: │ │ - useApi() hook │ │ attaches bearer │ ▼ ▼ NestJS API (Railway) │ Global pipeline: │ 1. Helmet, CORS, compression │ 2. ValidationPipe (whitelist + transform) │ 3. AuthGuard (verifies Clerk JWT, loads DB user) │ 4. PlatformTierGuard (org-tier feature gates) │ 5. ThrottlerGuard (60 req / 60s default) │ 6. LoggingInterceptor (Pino + Sentry scope) │ 7. CacheControlInterceptor │ 8. AllExceptionsFilter (Sentry + structured error body) │ Global pipeline: │ 1. Helmet, CORS, compression │ 2. ValidationPipe (whitelist + transform) │ 3. AuthGuard (verifies Clerk JWT, loads DB user) │ 4. PlatformTierGuard (org-tier feature gates) │ 5. ThrottlerGuard (60 req / 60s default) │ 6. LoggingInterceptor (Pino + Sentry scope) │ 7. CacheControlInterceptor │ 8. AllExceptionsFilter (Sentry + structured error body) PostgreSQL (Drizzle ORM)

Public endpoints opt out of auth with @Public(). Health check (/health) and Clerk webhook (/webhooks/clerk) are the canonical examples. See architecture/api.md for the full pipeline.


Real-time / WebSocket flow

Browser, fitkit-mobile ⇄ Socket.IO ⇄ apps/api (NestJS WS gateways) Redis (pub/sub) Other apps/api instances

apps/api/src/main.ts wires the Socket.IO Redis adapter when REDIS_URL is set. Without Redis the server falls back to the in-process adapter — single-instance only. Used for messaging, typing indicators, presence, and live update fan-out.

Mobile uses socket.io-client with transports: ['websocket'] only and AppState-driven reconnect (foreground → connect, background → disconnect). See mobile.md for the lifecycle details.


Background jobs (BullMQ)

Producers (services in apps/api/src/**) ─→ BullMQ queues ─→ Redis ─→ Workers (in-process)

Queues registered today (greppable via BullModule.registerQueue):

ModuleQueue role
apps/api/src/exercises/Admin canonical exercise jobs
apps/api/src/ai/embeddings/Exercise / workout / program / member-profile embeddings (Voyage API)
apps/api/src/programs/Program-level async work
apps/api/src/workouts/Workout-level async work
apps/api/src/export/CSV / report exports
apps/api/src/import/Arbox / CSV imports (multi-phase)
apps/api/src/push-notifications/Expo push fan-out
apps/api/src/admin/services/admin-queues.service.tsAdmin queue inspection

Bull-Board UI (apps/api/src/bull-board/) exposes queue state internally.


In-process event bus

NestJS EventEmitterModule.forRoot() is enabled globally in app.module.ts. Subsystems emit domain events (e.g. membership.activated) and other modules subscribe via @OnEvent. Canonical example: the Forms module’s onboarding hook (apps/api/src/forms/forms.service.ts Onboarding auto-issue block) listens for membership activation and auto-issues configured compliance forms.

This is intentionally separate from BullMQ — events are in-process, synchronous within the worker, no persistence. Use BullMQ when you need retries, scheduling, or cross-process delivery.


File storage

Cloudflare R2. Two logical buckets:

Bucket envUse
R2_BUCKET_NAMEGeneral attachments, exports, uploads.
R2_COMPLIANCE_BUCKET_NAMESigned compliance PDFs (FIT-158). 7+ year retention, immutability, no-delete — configure lifecycle + object-lock at bucket level. Falls back to R2_BUCKET_NAME when unset (dev only).

Module: apps/api/src/r2/. All reads presign a fresh URL (1h default) — no public URLs are served.


Observability

ToolScopeWired in
SentryAPI + Web + Marketing + Minisitesapps/api/src/instrument.ts, Next.js Sentry SDK on web
PostHogFrontend events + server-side payment funnel + Spotter cost telemetryapps/web/src/providers/posthog-provider.tsx, apps/api/src/event-tracking/
Pino logsAPI structured loggingapps/api/src/app/app.module.ts (LoggerModule.forRoot) + LoggingInterceptor
Bull-BoardJob queuesapps/api/src/bull-board/
Health endpointDB + Redis livenessapps/api/src/app/health.controller.ts (GET /health)

See architecture/observability.md for the deep dive (sample rates, beforeSend filters, audit-log gap).


Deployment

AppHostNotes
apps/apiRailwayapps/api/railway.toml. PR previews via .github/workflows/deploy-pr-preview.yml — vercel[bot] preview URL triggers a Railway preview environment deploy.
apps/webVercelvercel.json at repo root. Next.js framework preset.
apps/marketingRailwayapps/marketing/railway.toml. Astro adapter.
apps/adminVercelapps/admin/vercel.json. Internal-only.
apps/minisitesVercelapps/minisites/vercel.json. Custom domains attached via Vercel Domain API (VERCEL_TOKEN, VERCEL_MINISITES_PROJECT_ID).

PostgreSQL and Redis in production are managed (Neon for DB is the documented default; Railway managed Redis). Local dev uses docker-compose.yml — Postgres on 5432 (pgvector image), Redis on 6379. Test stack uses docker-compose.test.yml — Postgres on 55432, Redis on 56379.


  • api.md — NestJS modules, global pipeline, DI tokens.
  • web.md — Next.js routing, providers, data helpers, feature gating.
  • database.md — schema domains, migration policy, encryption.
  • auth.md — Clerk on both sides, multi-org, webhooks.
  • i18n.md — locale model, RTL, dictionary discipline.
  • observability.md — Sentry/PostHog/Pino + audit-log gap.
  • infra.md — hosting, env vars, CI workflows, secrets.