
Headless Commerce at B2B Scale
Under Armour · 35% ticket-resolution reduction
Case Study

A side project I keep coming back to
In 2019, as a thesis for my first graduate program, I built a fantasy draft game. It was split across two repos — a Laravel 6 GraphQL API and a React 16 SPA — and when I turned it in, it worked. It also never shipped to a single real user.
Six years later, that thesis has become Draft Slot — a prediction game scored against live events I don't control, running drafts across the NFL, NBA, MLB, and NHL. It has real users and infrastructure that scales up for draft night and back down for the offseason. It's the project I keep teaching myself new things on — and the one I've never once paid to market.
The original version was two repos, both dated early 2019. The API was Laravel 6 with Lighthouse GraphQL and Passport for OAuth. The frontend was React 16 with Apollo Client for data and Redux for local UI state, bundled with a hand-rolled Webpack config.
I picked every piece for a reason: GraphQL was new, Apollo was new, and picking them meant I'd learn them. That was the entire point of a thesis project.
It delivered the thesis. It did not deliver a product. I submitted the work, graduated, and the repos went quiet for almost two years.

When I came back to it, I didn't port the old code. I rebuilt the whole thing in a single Laravel monolith with Blade templates, Alpine.js for interactivity, and Tailwind for styling. The GraphQL layer was gone. The SPA was gone.
The choice looked unfashionable on paper. In practice, it was the only one that made sense. I had no team, no budget, no investors, and a full-time job. Every hour I spent tuning a bundler or fighting hydration was an hour I wasn't spending on features my users could actually see.
This was the version that actually shipped. Real users, a real database, a real draft night — the baseline the rest of this story is built on top of. The tradeoff was real too: I gave up some of the polish a full SPA offers. What I got back was velocity. Shipping something on a Sunday night became normal again.
It's worth naming how genuinely weird this problem is. Draft Slot is a prediction game scored against a live event I don't control — a draft run by someone else, delivered through a feed that sometimes lags or breaks. Four sports means four calendars and four different shapes of draft night. And the traffic is all cliff: dead for most of the year, mobbed for a few nights in April, June, July, and October.
Every architecture decision in this story exists because of that shape.

The Reset shipped, but it couldn't carry the problem. It was single-sport. There was no real-time board. The infrastructure didn't match a traffic curve that spikes four nights a year. So I rebuilt the live app's frontend and backend — the product never restarted; the codebase did.
What changed most was the process. Every change ran the same path — requirements first, build, validation before merge — over and over, until the single-sport baseline had become a multi-sport, real-time product that scales.
The loop itself was agentic — the Laravel codebase is 100% built with Claude, held to the same bar. That practice is its own story; this one is about what it produced: a codebase that hasn't missed a draft night since the rebuild.


















The board is the product, so the board got the design attention. Building a prediction is direct manipulation, not form-filling — and the same interactions had to survive the trip from a wide desktop grid to a phone.
Keeping the interaction model intact across breakpoints cost more than a simplified mobile view would have. It was the right bill to pay: draft night traffic is heavily mobile, and the product is the feel of making a pick.
What the rebuild produced is a real product running across the NFL, NBA, MLB, and NHL. The work splits into three jobs that all have to hold at once: keep draft night live, treat the unglamorous plumbing seriously, and keep the bill sane in the months nobody is here.
Live drafts, in real time. On draft night every user watches the same board, and every pick has to reach every connected browser in seconds.
Built like a real product. The plumbing users never see is the plumbing that decides whether this thing survives.
Paying for the quiet months. That cliff-shaped traffic curve is the whole infrastructure story — so I encoded the calendar into the deployment itself.
It's the kind of cost optimization a small company would do for real money. For me, it's what keeps the project inside the budget of a personal side project.
The next chapter moves off a single Laravel monolith and toward microservices that scale independently — the parts that spike on draft night decoupled from the parts that don't. Alongside that, I'm planning a transition to FastAPI, pulling the heavier service work into Python where the async story and the typing fit the direction the platform is headed.
The product splits in two as well. One side becomes a focused mock-draft app — the prediction game at its core. The other becomes a contest app called draftrm, where the competition and the sponsorship live: prize pools, sponsor configuration, and the Draft for a Cause rooms that let brands fund the payout.
The best thing about a side project is that it moves on your schedule. The scary thing about a side project is that it moves on your schedule. The work now is keeping the craft intact while finally letting more people in.