Skip to content

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

ServicePackagePortTypeImage
api@playpals/api4000Express RESTregistry.digitalocean.com/playpalzproduction/api
esu@playpals/esu4010Socket.IO…/esu
ogun@playpals/ogunBullMQ worker…/ogun
anansi@playpals/anansi4005Cron + admin HTTP…/anansi
igdb-heartbeat@playpals/igdb-heartbeatCron…/igdb-heartbeat
web@playpals/web3000Next.js 15…/web
mobile@playpals/mobileExpo / React NativeEAS build
docs@playpals/docs5173VitePress…/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

PackagePurpose
@playpals/dbPrisma schema, generated client, and query helpers. Imported by every backend service.
@playpals/queueThe BullMQ queue definition, so producer and consumer cannot disagree on its name or retry policy.
@playpals/typesShared types, including the socket event contract between the app and esu.
@playpals/socket-clientTyped Socket.IO client plus the useRealtime React hook the mobile app uses.
@playpals/transactionalEmail templates. Not yet wired to a provider.
@playpals/tsconfigsShared TypeScript configurations.

Choosing where code goes

The work is…It goes in
A new REST endpointapi
A new realtime eventpackages/types + esu, triggered from api
Slow, retryable, or CPU-heavyogun (or a new worker on the same queue)
Money, ledger, or payout relatedanansi
Scheduled and independent of user requestsA cron service, following igdb-heartbeat
Reusable across two or more servicespackages/*

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

ServiceEndpointHealthy response
apiGET /health{"status":"ok"}
esuGET /health{"ok":true}
anansiGET /healthHealth payload (unauthenticated)
webGET /200
ogunNo HTTP surface; liveness is inferred from logs and queue drain
igdb-heartbeatGET /health200

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.

Internal documentation — PlayPalz platform