Monorepo Layout
PlayPalz is a single repository managed by pnpm workspaces and Turborepo. The workspace globs are declared in pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"Top-level tour
playpalz/
├── apps/ # deployable units
│ ├── api/ # Express REST API :4000
│ ├── esu/ # Socket.IO realtime server :4010
│ ├── ogun/ # BullMQ media processing worker (no port)
│ ├── anansi/ # Creator payouts / accounting :4005
│ ├── igdb-heartbeat/ # Scheduled IGDB game catalog sync
│ ├── web/ # Next.js 15 marketing site :3000
│ ├── mobile/ # Expo / React Native app
│ └── docs/ # This VitePress site :5173
├── packages/ # shared libraries (not deployed alone)
│ ├── database/ # @playpals/db — Prisma schema + client
│ ├── queue/ # @playpals/queue — BullMQ queue definitions
│ ├── types/ # @playpals/types — shared TS types + socket contract
│ ├── socket-client/ # @playpals/socket-client — typed Socket.IO client + React hook
│ ├── transactional/ # email templates
│ └── tsconfigs/ # shared TypeScript configs
├── infra/
│ ├── terraform/ # DigitalOcean infrastructure as code
│ ├── k8s/ # Kubernetes manifests + sealed secrets
│ ├── helm/ # Loki logging stack values
│ └── ansible/ # LiveKit droplet provisioning
├── scripts/ # build-and-push scripts, one per service
├── dev-notes/ # working notes, plans, and implementation reports
├── specs/ # feature specs
├── docker-compose.yaml # local Postgres, Redis, LiveKit
├── docker-compose.dev.yaml # live-reload overlay for backend services
└── turbo.json # task graphWorkspace dependency graph
The important consequence: packages/* must be built before backend services will start, because services import from dist/, not from source. pnpm build at the root handles this via Turborepo's dependsOn: ["^build"].
Naming and the workspace protocol
Every internal dependency uses the workspace protocol so pnpm links the local copy rather than resolving from npm:
"@playpals/db": "workspace:*"Package names are @playpals/<name>. Note that the repo directory and the git remote are spelled playpals (with an s), while the product and production domain are playpalz (with a z). Both spellings appear in the codebase; the z spelling is correct for anything user-facing or infrastructure-related.
Turborepo tasks
turbo.json defines four tasks:
| Task | Behaviour |
|---|---|
build | Depends on ^build (upstream packages first). Caches .next/** and, for the docs site, .vitepress/dist/**. |
lint | Depends on ^lint. |
check-types | Depends on ^check-types. |
dev | cache: false, persistent: true — long-running processes. |
Filter to a single package with --filter:
turbo build --filter=@playpals/api
turbo dev --filter=@playpals/mobileOr via pnpm, which is equivalent for single-package scripts:
pnpm --filter @playpals/api devWhere things live inside a backend service
Every Express service (api, esu, anansi, igdb-heartbeat) follows the same shape:
src/
├── server.ts # entrypoint — starts the HTTP listener
├── app.ts # Express app assembly: middleware, routers
├── configs/index.ts # typed env config object
├── router/ # route definitions only — path → controller
├── controllers/ # request/response handling, validation
├── services/ # business logic; no Express types in here
├── middleware/ # auth, error handling, uploads
├── lib/ # cross-cutting helpers (logger, redis, jwt, s3)
└── types/ # request augmentation and shared interfacesWhen you add a feature, that is the order you touch them: router → controller → service. See Coding Standards.
Gotchas
packages/database/generated/is git-ignored and produced byprisma generate. A fresh clone will not typecheck until you run it.apps/api/src/controllers/channelOLD.tsis dead code kept for reference. Do not extend it.bcrpyt(typo) is listed alongsidebcryptinapps/api/package.json. Onlybcryptis used.
