Skip to content

System Overview

PlayPalz is a set of small, single-purpose services around one PostgreSQL database and one Redis instance, serving a React Native client. There is no service mesh, no event bus, and no CQRS — the services talk to each other over plain HTTP and Redis, and every one of them reads the same database through the same Prisma client.

That simplicity is deliberate and worth preserving.

The whole system

Rendering diagram…

The services

ServicePackagePortKindOwns
api@playpals/api4000HTTPEvery REST endpoint; the only writer for most domains
esu@playpals/esu4010WebSocketSocket.IO connections, presence, typing, live fan-out
ogun@playpals/ogunWorkerImage variants and Mux video asset creation
anansi@playpals/anansi4005Cron + HTTPCreator earnings, ledger, Stripe payouts
igdb-heartbeat@playpals/igdb-heartbeatCronSyncs the game catalog from IGDB
web@playpals/web3000HTTPPublic marketing site
mobile@playpals/mobileClientThe product

Design principles you should follow

One database, one client. Every service imports @playpals/db. There is no per-service schema and no service-to-service data API for reads. Adding a service means adding another consumer of the same Prisma client.

The API is the write path. esu deliberately does not write messages — the client POSTs to the API, the API persists and then asks esu to fan out. That keeps persistence and authorization in one place and makes the realtime layer stateless and disposable.

esu does not do authorization itself. It calls back into the API (/api/v1/dm/:id/authz, /api/v1/room/:id/authz) on every join. One implementation of the access rules, not two.

Heavy work is queued. Image resizing and video transcoding never happen in a request. The API enqueues to BullMQ and returns immediately; ogun does the work and pushes a realtime update when it finishes.

Redis is three things. BullMQ queue backend, Socket.IO adapter pub/sub (so any pod can reach any connection), and a response cache for the expensive discovery and trending queries.

Request paths at a glance

What the user doesPath through the system
Logs inMobile → API → Postgres → JWT back to the client
Opens the feedMobile → API → Redis cache, falling through to Postgres
Posts a photoMobile → API → Spaces + Postgres → BullMQ → ogun → Spaces → Postgres → esu → Mobile
Sends a DMMobile → API → Postgres → esu /admin → Socket.IO room → recipient
Joins a voice roomMobile → API (LiveKit token) → LiveKit droplet over WebRTC
Goes liveMobile → API (Mux stream credentials) → RTMP to Mux → viewers over HLS
Subscribes to a creatorMobile → RevenueCat IAP → webhook → API → Postgres
Gets paidanansi cron → Postgres ledger → Stripe transfer

Each of these is expanded on its own page: Authentication, Realtime, Media Pipeline, Livestreaming & Voice, Monetization.

What is deliberately absent

  • No API gateway. nginx-ingress routes by hostname; there is no aggregation layer.
  • No message broker. Redis pub/sub via the Socket.IO adapter is the only asynchronous fan-out.
  • No GraphQL. REST with domain-grouped routers under /api/v1.
  • No server-side rendering of app content. The Next.js app is a marketing surface; the product is the mobile client.
  • No CI/CD pipeline. Images are built and pushed from a developer machine with scripts/build-*.sh. See Deploying.

Internal documentation — PlayPalz platform