SunDrSunDr
Back to Blog
case-study

Poker Analytics SaaS: A Full-Stack Case Study

How one developer built a 67K-line poker analytics SaaS with 43 API endpoints, Monte Carlo simulations, and Paddle billing — from zero to production.

Published March 27, 20269 min read
TypeScriptSaaSNext.jsPostgreSQLCase StudyMonte CarloPaddleFull-StackAPI DesignTesting
Poker Analytics SaaS: A Full-Stack Case Study

I spent the last year and a half building a SaaS poker tournament tracker from the ground up. Not a weekend project, not a CRUD app with a nice landing page. I'm talking 67,760 lines of TypeScript, 43 REST API endpoints, 19 database models, Monte Carlo variance simulation, hand history parsing with cursed date formats, and a subscription billing system that nearly broke my brain. This is a case study of what full-stack web app development actually looks like when the product has real complexity.

The Challenge: Building a Professional Poker Analytics SaaS

The idea sounds straightforward: let poker players track their tournament results, analyze performance, and understand whether their wins and losses are skill or variance. In practice, this is one of the most technically dense SaaS products I've built.

Start with the core analytics. Poker players obsess over whether they're running above or below expectation. To answer that properly, you need a Monte Carlo variance simulator that runs thousands of simulated tournament histories using bootstrap resampling on the player's actual results. You're generating confidence bands, calculating downswing probability, estimating bankroll requirements — this isn't a SUM(profit) query. It's computationally heavy statistical modeling running in the browser.

Then there's hand history import. Poker sites like PokerStars and GGPoker export tournament data in their own text formats, and players expect to drag a file in and see their results instantly. These parsers need to handle multiple currencies, deduplication of re-imports, and — as I'd painfully discover — about 15 different timezone formats because PokerStars apparently decided date consistency was optional.

Layer on a multi-currency system supporting 25+ currencies with dual-fallback exchange rates, 30 achievements with a 5-tier unlock system that evaluates in real time, 6 supported languages, PWA with offline support, and full subscription billing. That's the product.

Architecture: Next.js Full-Stack with 19 Database Models

I went with Next.js App Router for this — a Next.js SaaS architecture that handles both the frontend and API layer in a single deployable unit. For a solo developer building a complex product, having your API routes, server components, and client UI in one repo eliminates a lot of deployment and coordination overhead.

The data layer is Prisma ORM talking to PostgreSQL. 19 models covering users, tournaments, achievements, bankroll, subscriptions, staking contacts, and feature flags. Prisma's type-safe queries are a huge win when you have this many relations — the TypeScript compiler catches broken queries before they ever hit the database.

The 43 REST API endpoints cover everything from tournament CRUD to achievement evaluation, import processing, subscription management, and AI assistant chat. Every endpoint has strict TypeScript types end to end. No any. If the API response shape changes, the compiler tells me everywhere that breaks.

I built a feature flag system controlling 7 modules, which lets me gate Pro features cleanly and roll out new functionality gradually. The app is also a PWA using Serwist for offline support — poker players often want to log results at the table, and cell service in casinos is notoriously terrible. Auth covers Google OAuth, Facebook OAuth, and email/password with verification, because different users have strong preferences and you lose signups if you force one method.

One decision I'd make again without hesitation: 24 custom React hooks. Extracting complex stateful logic — tournament filtering, currency conversion, achievement tracking, variance simulation — into composable hooks kept the components readable and the business logic testable in isolation.

The Hard Parts: Variance Simulator and Hand History Parsers

The Monte Carlo simulation is the product's flagship feature and the hardest piece of pure engineering in the codebase. The simulator takes a player's actual tournament results and uses bootstrap resampling to generate thousands of possible outcome paths. From those paths, it calculates confidence bands at multiple percentile levels, downswing probability distribution, expected bankroll requirements, and probability of profit after N tournaments.

Getting this performant in the browser required careful optimization. The naive approach — just loop 10,000 times and resample — is too slow for large result sets. The statistical accuracy had to be validated against known poker variance calculators, which meant building a separate test suite just for the math.

The hand history parsers were a different kind of hard. PokerStars files use a text-based format that looks simple until you start parsing real-world data. I discovered that PokerStars has changed their timestamp format multiple times over the years, resulting in about 15 timezone representations across different file versions. Some use abbreviations like "ET", some use full IANA zones, and some use offsets.

The GGPoker parser adds multi-currency complexity. A single player might have tournaments in USD, EUR, and local currency mixed in one export file. The parser handles deduplication for re-imports and normalizes everything into the user's preferred currency using the dual-fallback exchange rate system.

Subscription Billing: Paddle Integration with Race Condition Handling

I chose Paddle for subscription billing because it handles VAT calculation, tax remittance, and merchant of record responsibilities globally. When you're a solo developer doing SaaS development, the last thing you want is to become an expert in EU VAT law.

The billing model has three tiers: Free (with a 1,000 tournament limit), Monthly, Yearly, and Lifetime. The Paddle payment integration itself was relatively clean, but the real engineering challenge was the webhook system.

Here's the problem that nearly broke me: when a user upgrades from a monthly subscription to a lifetime purchase, Paddle fires multiple webhooks — a subscription cancellation and a one-time payment confirmation. These can arrive in any order, sometimes milliseconds apart. If the cancellation webhook processes after the lifetime activation, the user loses their access. If they arrive simultaneously, you get a database race condition.

I solved this with idempotent webhook handlers, explicit priority ordering — lifetime purchases always win over cancellations regardless of webhook arrival order — and an auto-sync mechanism that periodically reconciles local subscription state with Paddle's API, catching any webhooks that got lost in transit. The feature gating system controls 6 Pro features and enforces the free tier's 1,000 tournament cap.

Testing: 1,400+ Tests Across Three Layers

This project has 1,400+ test cases spread across 341 test files and 26 end-to-end scenarios. That's not because I enjoy writing tests — it's because shipping billing bugs in a SaaS product means real users lose real money or access.

The testing strategy has three layers. Unit tests (Jest) cover the statistical functions, currency conversion, hand history parsing, and achievement evaluation logic. Integration tests use Prisma against a PostgreSQL Docker container, testing the actual API endpoints with real database operations. The E2E layer uses Playwright for 26 critical user flows: signup, tournament creation, import processing, subscription purchase, feature gating enforcement.

For a SaaS product with subscription billing, my rule is simple: if it touches money or access control, it has tests. No exceptions.

Building a SaaS Product? Here's What I Learned

This project is a good example of what full-stack web app development looks like when done right — strict TypeScript everywhere, comprehensive testing, robust error handling, and billing systems that don't break under real-world conditions. The hard parts of SaaS development are rarely the ones you expect. The Monte Carlo simulation was intellectually challenging but predictable. The Paddle webhook race conditions were a multi-day debugging nightmare.

I've been building software professionally for 9+ years, and the platforms I've worked on serve 80M+ viewers. Every project — whether it's a poker analytics SaaS, a Smart TV streaming app, or a web platform — gets the same level of engineering rigor. If you're planning a SaaS product or any project where reliability matters, book a free 30-minute consultation or try the project calculator to get a rough estimate.

Have a project in mind?

Book a free 30-minute call to discuss your project, or try the calculator for a quick estimate.

Aleksandr Sakov

Aleksandr Sakov

Founder of SunDr. 9+ years building OTT streaming platforms, mobile apps, and web applications. The platforms I've built serve 80M+ viewers across 15+ device types.