The Problem
Every gamer I know has the same frustration: your stats are scattered across five different websites. Steam has your hours. Riot has your League rank. Blizzard buries everything behind menus you have to dig through every time. There’s no single place that shows the full picture of how you actually game — across all your platforms, all your games, all at once.
I got tired of the problem and decided to build the solution myself.
That’s how GamerStats.gg was born.
What It Does
GamerStats is a cross-platform gaming profile. You connect your accounts — Steam, Riot (League of Legends), Battle.net (World of Warcraft) — and it generates a “wrapped” report for each one. Top games, total hours, rank, KDA, raid progress. The kind of stuff that actually means something.
On top of that:
- Compare mode — put any two players side-by-side across Steam and LoL
- AI chat — ask questions about any player’s stats, powered by Claude
- Shareable profiles — one public URL for your full cross-platform gaming identity
- Leaderboards — community rankings for Steam hours, LoL rank, and Gamer Score
The Stack
This was a proper full-stack build from scratch. No starter templates, no low-code shortcuts.
Frontend: React + Vite, deployed on Vercel. I leaned into lazy loading and code splitting pretty early — some of the wrapped report pages are heavy, and I didn’t want to tank load times on the home page.
Backend: Python with Flask, deployed on Railway. I went with Flask because I wanted full control without the overhead of Django. Railway made it easy to run the backend alongside a managed PostgreSQL instance.
Database: PostgreSQL. Users, sessions, linked accounts, invite codes, chat history — all relational, all normalized. I had to think carefully about how account linking worked, especially the case where someone had already used a Steam ID as a guest before creating an account.
AI: Anthropic’s Claude API. The AI chat feature lets you ask questions like “What’s this player’s win rate on Jinx?” or “Is this person a one-trick?” Claude handles it surprisingly well when you give it structured game data as context.
Integrations
This is where most of the complexity lived.
Steam Web API was the first integration and the most straightforward. Valve has a solid public API — you can pull owned games, playtime, recent activity, and player summaries with just an API key and a Steam ID.
Riot Games API (League of Legends) was more involved. Summoner lookups, match history, champion mastery, ranked stats — each requires separate API calls and careful rate-limit handling. I also had to handle the Riot ID format (gameName#tagLine) and map it to a PUUID before I could do anything else.
Battle.net OAuth (World of Warcraft) was the trickiest. Blizzard’s OAuth flow requires the user to actually authorize your app, so I had to build a proper OAuth callback, store access tokens, handle token expiry, and support multiple regions (US, EU, KR). The WoW API then gives you character data, raid progress, and Mythic+ ratings.
Discord OAuth — users can sign in with Discord instead of email/password, which made onboarding smoother for the target audience (gamers, who all have Discord).
Lessons Learned
Start with the data, not the UI. I made the mistake early on of building out components before I’d fully mapped out what data the API would return. Every time the shape of the data changed, I had to redo the UI. Now I always sketch the data model first.
Email is harder than it looks. I built my own email verification flow — sending confirmation links, handling expiry, dealing with edge cases like already-verified accounts. Then I added a newsletter opt-in (via Buttondown) where subscribers get added automatically after verifying their email. Each piece was straightforward in isolation, but wiring them together cleanly took real thought.
Production always breaks things CORS never caught in dev. I had fetch calls throughout the frontend that worked fine locally pointing at localhost:5050 and silently broke the moment they hit production because they weren’t using the API base URL. Lesson learned: always use an environment variable for the API base, even in development.
OAuth is not just one thing. I had three different OAuth flows in this project (Discord, Steam, Battle.net) and each worked differently. Discord is standard OAuth 2.0. Steam uses OpenID. Battle.net is OAuth 2.0 but with Blizzard-specific quirks around regions and token scopes. Reading the actual docs carefully instead of cargo-culting examples saved me a lot of pain.
AI context matters more than prompt engineering. The quality of the AI chat feature comes almost entirely from the structured game data I pass in as context, not from clever prompt tricks. Give Claude accurate, well-organized player data and it reasons about it well. Give it a blob of raw JSON and it struggles.
What I’d Do Differently
I’d design the account-linking system more carefully upfront. The “orphan merge” problem — where someone uses a Steam ID as a guest, then later creates an account and wants to link the same Steam ID — required a fair bit of refactoring to handle cleanly. If I’d thought through all the account states from day one, it would have been simpler.
I’d also set up proper logging and monitoring from the start. Railway gives you some basic logs, but I was flying blind for a while on production errors. A proper structured logging setup would have saved me some debugging sessions.
What’s Next
The project is in active development. The immediate roadmap includes Overwatch 2 stats, a weekly gaming digest email, and profile badges for achievements like “1,000 hours in one game.” Eventually I want to add Valorant, Xbox achievements, and PlayStation trophies.
The full roadmap is at gamerstats.gg/roadmap.
Building GamerStats has been the most technically complete personal project I’ve shipped. Every part of the stack — auth, database, multiple APIs, AI, email, production deployment — was something I had to figure out from scratch or learn as I went. It’s rough around the edges in places, but the core works, and it’s only going to get better.
If you’re a gamer, go check it out. If you build something similar, feel free to reach out.
Cheers,
Paul
Blog