Skip to content

Monorepo Layout

PlayPalz is a single repository managed by pnpm workspaces and Turborepo. The workspace globs are declared in pnpm-workspace.yaml:

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 graph

Workspace dependency graph

Rendering diagram…

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:

json
"@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:

TaskBehaviour
buildDepends on ^build (upstream packages first). Caches .next/** and, for the docs site, .vitepress/dist/**.
lintDepends on ^lint.
check-typesDepends on ^check-types.
devcache: false, persistent: true — long-running processes.

Filter to a single package with --filter:

bash
turbo build --filter=@playpals/api
turbo dev   --filter=@playpals/mobile

Or via pnpm, which is equivalent for single-package scripts:

bash
pnpm --filter @playpals/api dev

Where 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 interfaces

When 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 by prisma generate. A fresh clone will not typecheck until you run it.
  • apps/api/src/controllers/channelOLD.ts is dead code kept for reference. Do not extend it.
  • bcrpyt (typo) is listed alongside bcrypt in apps/api/package.json. Only bcrypt is used.

Internal documentation — PlayPalz platform