Local Setup
Getting from a fresh clone to a running app. Budget about 15 minutes, plus download time.
Make sure you have everything in Prerequisites first.
1. Clone and install
git clone https://github.com/cryptixcoder/playpals.git
cd playpalz
pnpm installpnpm install links every workspace package. It does not build them — that comes next.
2. Create environment files
Each service reads its own .env. Every one of them ships with a .env.example:
cp packages/database/.env.example packages/database/.env
cp packages/queue/.env.example packages/queue/.env
cp apps/api/.env.example apps/api/.env
cp apps/esu/.env.example apps/esu/.env
cp apps/ogun/.env.example apps/ogun/.env
cp apps/anansi/.env.example apps/anansi/.envThen set the database URL in both packages/database/.env and apps/api/.env so it matches the Docker Compose Postgres:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres"And point Redis at the Compose instance — note the password, which is set in docker-compose.yaml:
REDIS_URL="redis://:playpalz@localhost:6379"The Redis password is easy to miss
docker-compose.yaml starts Redis with --requirepass playpalz. If your REDIS_URL omits the password you will get NOAUTH Authentication required from BullMQ and the Socket.IO adapter, which surfaces as media never finishing processing and realtime silently not working.
The minimum you must fill in for a working local app is DATABASE_URL, REDIS_URL, and JWT_SECRET (the same value in apps/api/.env and apps/esu/.env — the realtime server verifies tokens the API issued). Everything else can stay blank until you need that integration. See Environment Variables for the complete matrix.
3. Start infrastructure
docker compose up -d db redisThat gives you:
| Container | Image | Port |
|---|---|---|
playpals-db | postgres:15.4 | 5432 |
playpals-redis | redis:7 | 6379 |
Add livekit to the command if you are working on voice rooms:
docker compose up -d db redis livekit4. Generate the Prisma client and migrate
cd packages/database
pnpm db:generate # generates the client into packages/database/generated/prisma
pnpm db:migrate # applies migrations, then re-generates
cd ../..Wait a few seconds after docker compose up before running these — Postgres needs to finish initialising or the migration will fail to connect.
5. Build the shared packages
pnpm buildBackend services import @playpals/db, @playpals/queue, and @playpals/types from their compiled dist/ output, so this step is required before the services will start. Turborepo caches it, so subsequent runs are near-instant.
6. Seed data (optional but recommended)
cd packages/database
pnpm db:seed
cd ../..An empty database gives you an empty feed, an empty explore page, and no one to message. Seed unless you specifically need a blank slate.
7. Run the services
The root dev script starts the four services you usually need at once:
pnpm devThat runs api, esu, ogun, and mobile through Turborepo's TUI. Use pnpm dev:all to include web, anansi, and igdb-heartbeat.
To run one service on its own:
pnpm --filter @playpals/api dev # REST API → :4000
pnpm --filter @playpals/esu dev # Socket.IO → :4010
pnpm --filter @playpals/ogun dev # media worker (no HTTP port)
pnpm --filter @playpals/anansi dev # payouts → :4005
pnpm --filter @playpals/web dev # Next.js → :3000Confirm the API is alive:
curl http://localhost:4000/health
# {"status":"ok"}8. Run the mobile app
The app needs a development build installed in your simulator — Expo Go cannot load the native modules this app depends on. Unzip the development client build and drag it onto the running iOS Simulator, then:
pnpm --filter @playpals/mobile iosPoint the app at your machine with apps/mobile/.env. All three variables are read in apps/mobile/lib/env.ts, and the app logs an error at startup if the first two are missing:
EXPO_PUBLIC_ENV=development
EXPO_PUBLIC_API_URL=http://localhost:4000/api/v1
EXPO_PUBLIC_REALTIME_URL=http://localhost:4010EXPO_PUBLIC_API_URL must include /api/v1
It is used verbatim as the axios baseURL (apps/mobile/lib/axios.ts). Every call in apps/mobile/api/ is written relative to it, so dropping the suffix produces a wall of 404s.
Physical device on the same Wi-Fi
localhost resolves to the device itself, not your laptop. Use your machine's LAN address (http://192.168.x.x:4000/api/v1) and make sure your firewall allows inbound connections on 4000 and 4010.
Alternative: run the backend entirely in Docker
docker-compose.dev.yaml overlays live-reload containers for api, esu, and ogun on top of the base Compose file. This is slower than running natively but removes "works on my machine" drift:
./start-dev.sh # foreground, all services
./start-dev.sh -d api # detached, just the APIYou are running when…
curl http://localhost:4000/healthreturns{"status":"ok"}curl http://localhost:4010/healthreturns{"ok":true}- The mobile app opens to the login screen and you can register an account
- Creating a post with an image results in thumbnails appearing after a few seconds (that is
ogunworking)
If any of those fail, see Troubleshooting.
