The web app cannot work without the API. Login, organizations and all domain data are proxied to the core API on port 3031. Set up the backend first.

Prerequisites

Clone the workspace

Each repository is a separate clone inside one workspace folder, on the dev branch.
The folder names matter. The root scripts and the web app’s dev script look for ../backend and ../tools. Ask a teammate for the root package.json and the tools folder, since they are not inside any of the three repositories.

Backend

1

Install dependencies

2

Create the env file

The backend README tells you to copy .env.example, but that file is not in the repository at the time of writing. Create backend/.env by hand using Backend environment variables. The minimum set that passes validation is:
Set PORT=3031 explicitly. The schema default is 8000, but every script in the workspace expects 3031.packages/config loads the env with dotenv from the current working directory. pnpm dev:api runs inside apps/core-api, and Prisma commands run inside packages/db or packages/database. Existing checkouts keep a copy of the same .env in backend/, backend/apps/core-api/, backend/packages/db/ and backend/packages/database/. Copy your file to those four places, or symlink them.
3

Start Postgres and Redis

Any local Postgres 16 and Redis will do. The web repository ships a docker-compose.yml with a Postgres 16 container (and MinIO) left over from the starter kit. The backend has no compose file. A minimal setup with Docker:
4

Generate the Prisma clients

This runs prisma generate in both @perform/db and @repo/database. It does not need a database connection.
5

Create the schema

db:migrate is prisma migrate deploy against packages/db/prisma/schema.prisma. It applies the hand-written migrations in packages/db/prisma/migrations.
pnpm db:migrate runs against whatever DATABASE_URL points to. Check the URL before you run it. Never point a local env file at a shared or production database for convenience.
On a completely empty database this command is not enough on its own. The migration history was written for the public schema, and at least one migration (20260823120000_user_phone_number) alters auth.user, a better-auth table that no migration creates. The auth schema is described only in packages/database/prisma/schema.prisma.The repository does not document a bootstrap path for an empty database, so treat the following as the practical route, not an official one. The manual deploy workflow creates and syncs tables with a schema push from @repo/database, whose schema contains both auth and public:
The simplest start is a sanitised dump from a teammate. Read Database migrations before you change a model.
6

Build the packages once

The API imports @repo/* packages through their dist output, so they must be built before the API can start or type-check.
7

Run the API

This runs nodemon, which watches apps/core-api/src and restarts tsx src/server.ts. Check it with:
Optional: pnpm db:seed runs packages/db/prisma/seed.ts.

Web app

1

Install dependencies

2

Create the env file

Create frontend/.env. There is no .env.example in this repository either. The scripts load it with dotenv -c, so .env.local overrides also work.
SERVICE_AUTH_SECRET must be identical in both repositories, or every /v1/web call fails with 401.
3

Run it

pnpm dev first runs ../tools/ensure-core-api.sh --start. If nothing answers on http://127.0.0.1:3031/healthz, it starts the backend for you in the background. Then Turborepo starts two dev servers: saas on port 3000 and marketing on port 3001.Use pnpm dev:app to skip the API check.
The web app needs no database of its own at runtime. In local development an anonymous visit to / is rewritten to the marketing dev server on port 3001 by apps/saas/proxy.ts. Go to /login to reach the app.

Run the API and the web app together

From the workspace root:
This starts the API, waits for /healthz, then starts the web app. Stopping one stops both.

Mobile app

1

Install dependencies

The README mentions npm install, but the repository pins pnpm in packageManager and CI installs with pnpm install --frozen-lockfile. Use pnpm so the patches in patchedDependencies are applied.
2

Create the env file

You can leave it empty. In development src/lib/api/config.ts reads the Metro host and calls http://HOST:3031. On the Android emulator it falls back to 10.0.2.2, on the iOS simulator to localhost.To force a URL, set EXPO_PUBLIC_API_URL. pnpm use-lan-ip writes your machine’s LAN address into .env for you. It reads the en0 interface by default. Set IFACE to use another one.
3

Start Metro

The app uses native modules that Expo Go does not include (HealthKit, Health Connect, widgets, the local cardio notification module). For full functionality use a development build:
4

Sign in

The trainee signs in with a phone number and a code sent over WhatsApp. With TRAINEE_OTP_DEV_MODE=true on the backend the code is not sent through SmartSend. The API logs it with the message trainee OTP dev mode: skipping WhatsApp send and returns it as devCode in the response, so you can test without WhatsApp credentials. The same flag makes the coach phone login log its code instead of sending it. The trainee’s phone must exist as a client in a studio first. Create one in the web app.

Ports

First sign-in on a fresh database

Sign up in the web app at http://localhost:3000. Creating an organization creates the matching Studio row the first time the web app calls the /v1/web lane. The owner is provisioned as a head coach at the same moment.