Back to Case Studies

Girowin: a real-money gaming platform

How I structured Girowin's platform: NestJS services per domain, a wallet with an idempotent ledger, messaging with RabbitMQ and operation across replicas.

Girowin: a real-money gaming platform

1. Context & Engineering Challenge

Operational Context:

Girowin is an online gaming platform with casino, original games, live casino, a wallet, promotions, affiliates and KYC, run from a full back office. I'm the CTO and built the architecture and much of the backend. On a platform like this every cent has to be right: the same deposit can never become balance twice, a bet can never be debited twice and the history must match the balance, even with providers resending callbacks and queues redelivering messages.

The Technical Bottleneck:

The problems that shaped the architecture: • Game providers and payment gateways resend calls; the queue redelivers messages. • Many concurrent bets on the same wallet. • Scheduled jobs running on several replicas at once. • Integrations with different requirements, including a fixed outbound IP. • A back office that needs to see everything: finance, risk, KYC and CRM.

2. Decisions

The main decisions: • Services per domain: auth, casino, gaming, payment, affiliates, communication and CRM, in a NestJS monorepo, each with its own PostgreSQL database. • Idempotent credits: every credit carries the id of its source movement as a key, and a resend returns the original receipt. • Wallet ledger, currently rolling out: every movement records the balance before and after in the same transaction that changes the balance, with a unique key per wallet, kind and external id. • Explicit concurrency: bet debits with FOR UPDATE and withdrawals with an advisory lock per withdrawal and a debit receipt. • Events between services over RabbitMQ, with the client hardened against channel failures. • Workers separate from the API (web and worker roles), with crons coordinated by a Redis lease. • An outbound proxy with a fixed IP for providers that require an allowlist.

3. Architecture Topology & Flow

Girowin: a real-money gaming platform
Rendering architectural diagram...

4. Implementation & Design Patterns

I organized the backend as a NestJS monorepo with services per domain: auth, casino, gaming, payment, affiliates, communication and CRM, plus workers that run crons and queues without exposing HTTP. The front-ends talk REST to the services, realtime goes through Socket.IO and the services talk to each other over RabbitMQ, as when a confirmed deposit is credited to the wallet. Stack by layer: • Backend: NestJS 11 in TypeScript, as a monorepo of services • Front-ends: Next.js 16, React 19, Tailwind 4, TanStack Query and Zustand • Data: PostgreSQL with TypeORM, one database per service • Cache and locks: Redis • Messaging: RabbitMQ between services and Bull for jobs • Realtime: Socket.IO • Security: JWT in HttpOnly cookies, TOTP 2FA, KYC and rate limiting • Quality: Jest and Supertest, with wallet regression tests in CI (GitHub Actions) • Infra: Docker, NGINX and an observability stack with OpenTelemetry, Prometheus, Loki, Tempo and Grafana In practice: • A confirmed deposit fires the in-process deposit.confirmed event; the credit goes to gaming over a RabbitMQ RPC keyed by the deposit id and is retried with backoff. • Player notices go out from a transactional outbox with FOR UPDATE SKIP LOCKED, leases and exponential backoff. • In the ledger, a daily job checks the chain and flags any gap. • Provider callbacks are validated with HMAC-SHA256 over the raw body. • Sessions use SHA-256 hashed tokens and refresh token rotation with reuse detection. • Wallet regression tests run in CI against a real PostgreSQL.

5. Trade-offs & Conscious Compromises

What these choices cost: • One database per service gives isolation but means eventual consistency between services and well-designed events. • A ledger with a unique key removes duplicates at the cost of more writes and strict external id discipline in every integration. • Locks and receipts protect withdrawals but require a strict lock order to avoid deadlocks. • Separate workers make the API easier to scale, at the price of coordinating jobs across replicas. • Many services call for good observability to debug a flow that crosses the queue.

6. Results

Operating numbers are confidential. Here I focus on the architecture and the decisions that keep the money right.
Éverton Carvalho|Software Engineer | Software Architect | CTO

Built with Next.js 15, NestJS 11, PostgreSQL and Prisma. Decoupled, pragmatic architecture.

© 2026 Éverton Carvalho. Todos os direitos reservados.

Portfolio V2 · Zero Shared Runtimes