All writeups
Building in PublicFull-Stack DevelopmentNumDojo

Shipping a Real-Time Multiplayer SaaS Solo: What Building NumDojo Taught Me

July 10, 20266 min read

I audit smart contracts for a living, so people are usually surprised when they find out I spent about a year building a math learning platform. The two don't obviously connect. But the work that got me into security was never really about crypto. It was about how systems behave under pressure, where they leak, and where the gap between what something is supposed to do and what it actually does turns into a real problem. Building a product from nothing is the same question asked from the other direction. This is a writeup of what that actually took, solo, from the first empty repository to a site that went live a couple of days ago.

What NumDojo actually is

NumDojo is a place to learn math by doing it and competing at it. There are 180 lessons across 8 categories, and behind them more than two thousand practice problems, so the thing you just learned has somewhere to land instead of evaporating. A spaced-repetition review queue built on SM-2 decides what comes back and when. On top of the learning there's a competitive layer: real-time one-on-one duels matched by ELO, tournaments with brackets and leagues, 43 badges to earn along the way. Subscriptions run through Stripe. That's the honest version. It's a lot of moving parts for one person, and most of them had to work at the same time before any of it was worth showing anyone.

The architecture, and why each piece is there

The frontend is Next.js and React in TypeScript, styled with Tailwind, animated with Framer Motion, and it renders math with KaTeX because equations that look wrong don't teach anyone anything. There's Three.js in there too, which I'll come back to. The backend is Node and Express, also TypeScript, sitting in front of PostgreSQL. The data model grew past fifty tables, which sounds like a lot until you start counting everything a system like this has to remember: lessons, problems, attempts, review schedules, duels, tournament state, badges, subscriptions, usage. Redis handles the fast, ephemeral state that a database shouldn't be hammered for. Socket.io carries the real-time duels. The whole thing runs in Docker.

None of those choices were about chasing anything new. I picked tools I could reason about under load, because I was going to be the only one holding the whole picture in my head. When you're the person who gets paged and the person who has to fix it, boring and predictable is worth more than clever.

The hardest problem was the least glamorous one

The hardest part of the whole build wasn't the real-time duels or the billing. It was the lessons themselves, and specifically getting the backend to serve lesson content to the frontend correctly and reliably every single time. It's the part a visitor never thinks about, because when it works it's invisible. A lesson loads, the right problems attach to it, the right state comes back. But there is a lot of structure underneath that, and getting it to fetch cleanly, in order, without a piece going missing, took more retries than anything else in the project.

Matchmaking was the second hardest, and it was hard in a way I did recognize going in. It's not enough for the duel to feel right in the moment. The system has to actually pair players the way it's supposed to and then persist what happened correctly afterward, so ratings, results, and history all agree with each other after the dust settles. Real-time plus durable state is a nasty combination, because the moment that feels good to a player is the same moment where it's easiest to drop or corrupt data. Getting both halves right at once was most of the work there.

And there was a bigger reset before any of that. Early on I threw out roughly half of what I'd built. I'd been treating practice as a secondary feature, a nice-to-have bolted onto the lessons. Then it became obvious that cutting corners there made the whole thing harder for the student, not easier. Without a strong practice loop, learning doesn't stick, and everything else I was building was decoration on a weak foundation. So I stopped, deleted a lot of work, and rebuilt with practice at the center instead of the edge. That was painful in the moment and completely correct in hindsight.

Building it the way I audit

The clearest thing auditing gave me wasn't a checklist. It was a mindset: how to build something, how to secure it, and an instinct for where the problems are going to show up before they do. When you spend your days reading other people's code specifically to find the place they didn't think about, you stop being able to write your own code without that voice running in the background.

In practice that meant the security wasn't an afterthought I bolted on near launch. Authentication runs on JWTs, passwords are hashed with bcrypt, and every query to the database is parameterized, because SQL injection is an old, well-understood hole and there is no excuse for leaving it open. There's rate limiting on the endpoints that need it. And because the audience includes kids, the whole thing had to respect COPPA, which shapes what you collect and how you handle it rather than being a banner you slap on at the end. None of that is exotic. It's the same refusal to skip the boring parts that a good audit runs on. The difference is that this time I was the developer whose blind spots I was trying to cover, which is a strange and useful exercise.

What I'd do differently

Honestly, the main thing is that I'd take a harder look at newer tech and architecture choices before committing. Some of the ground I picked was chosen for how well I already knew it, and a year is long enough that I'd want to re-check those calls against what's available now rather than assume the safe path was the best one. I don't want to oversell this into a list of grand regrets I don't actually have. It mostly worked. But wearing every hat at once — dev, QA, pipeline, networking, security, all of it me — means some decisions got made fast so I could keep moving, and those are the ones I'd revisit with more time.

The thing I'm quietly proud of won't show up in any feature list. There's a 3D astronaut animation on the login screen, the Three.js work I mentioned earlier, and it's the kind of detail nobody asks for and everybody feels. Past that, it's the shape of the whole competitive layer: the way the XP, the belts, the leagues, and the duels line up so that becoming good at math starts to feel like leveling up in a game you actually want to keep playing. Making that feel easy and natural was a lot of deliberate work under the surface.

It's live

NumDojo is up and running at numdojo.com. It went live a couple of days before I wrote this, so it's early, and I'm not going to pretend otherwise with numbers I haven't earned yet. What I can say is that it's real and it works.

If any of this sounds like the way you'd want your own product built — the security thought through from the start, the boring parts not skipped, one person actually holding the whole picture — that's exactly how I work for clients too. Here's what that looks like.

Sources

Shipping something that needs this checked?

Get an independent smart contract audit before mainnet — manual review, Foundry testing, and fix verification.

Request an Audit