Service Index
Seven deployable apps and five shared packages. Each service page covers what it owns, how to run it, its configuration, and how it fails.
Services
| Service | Package | Port | Type | Image |
|---|---|---|---|---|
| api | @playpals/api | 4000 | Express REST | registry.digitalocean.com/playpalzproduction/api |
| esu | @playpals/esu | 4010 | Socket.IO | …/esu |
| ogun | @playpals/ogun | — | BullMQ worker | …/ogun |
| anansi | @playpals/anansi | 4005 | Cron + admin HTTP | …/anansi |
| igdb-heartbeat | @playpals/igdb-heartbeat | — | Cron | …/igdb-heartbeat |
| web | @playpals/web | 3000 | Next.js 15 | …/web |
| mobile | @playpals/mobile | — | Expo / React Native | EAS build |
| docs | @playpals/docs | 5173 | VitePress | …/docs |
What each one owns
api — the centre of gravity. Every REST endpoint, all authentication, all authorization, and the only writer for most domains. If you are unsure where logic belongs, it belongs here.
esu — holds WebSocket connections and fans out events. Stores nothing, decides nothing. Named for the Yoruba deity of language and exchange.
ogun — consumes the media-processing queue: image variants with Sharp, video assets with Mux. No HTTP surface at all. Named for the deity of iron and transformation.
anansi — calculates creator earnings, writes the double-entry ledger, and executes Stripe payouts on a two-phase monthly cycle. Named for the trickster spider associated with webs of connection.
igdb-heartbeat — keeps the game catalog in sync with IGDB on a schedule, and mirrors cover art into Spaces.
web — the public marketing site. Minimal; the product is the mobile app.
mobile — the product itself. Expo SDK 54 with expo-router.
Shared packages
| Package | Purpose |
|---|---|
@playpals/db | Prisma schema, generated client, and query helpers. Imported by every backend service. |
@playpals/queue | The BullMQ queue definition, so producer and consumer cannot disagree on its name or retry policy. |
@playpals/types | Shared types, including the socket event contract between the app and esu. |
@playpals/socket-client | Typed Socket.IO client plus the useRealtime React hook the mobile app uses. |
@playpals/transactional | Email templates. Not yet wired to a provider. |
@playpals/tsconfigs | Shared TypeScript configurations. |
Choosing where code goes
| The work is… | It goes in |
|---|---|
| A new REST endpoint | api |
| A new realtime event | packages/types + esu, triggered from api |
| Slow, retryable, or CPU-heavy | ogun (or a new worker on the same queue) |
| Money, ledger, or payout related | anansi |
| Scheduled and independent of user requests | A cron service, following igdb-heartbeat |
| Reusable across two or more services | packages/* |
Adding a whole new service is rarely the answer. The existing five cover HTTP, sockets, queues, and cron; most features are a new module inside one of them.
Health endpoints
| Service | Endpoint | Healthy response |
|---|---|---|
api | GET /health | {"status":"ok"} |
esu | GET /health | {"ok":true} |
anansi | GET /health | Health payload (unauthenticated) |
web | GET / | 200 |
ogun | — | No HTTP surface; liveness is inferred from logs and queue drain |
igdb-heartbeat | GET /health | 200 |
ogun has no health probe
The deployment cannot tell Kubernetes whether the worker is actually consuming jobs. A wedged worker looks identical to a healthy one. Adding a small HTTP server that reports queue connectivity would make it restartable on failure.
