skip to content
meistyr.tech/~luke
case study/002/archived

Royalty Series

The platform behind Royalty Series, a competitive Rocket League league: a NestJS API, a Discord bot and a Nuxt website, run in production for seven seasons.

role
Founder & Full Stack Engineer
period
Jan 2024 — May 2026
status
Archived case study
01 — overview

How the league ran

The league ran in Discord and Google Sheets. Match results came from exported replay data and player submissions, staff approved or denied them, and other staff built and maintained the sheets. The platform connected all of it through one API: the Discord bot sent staff and team actions to it, sheet data was imported into it, and the website read from it.

02 — the platform

The website

royalty.meistyr.tech/circuits
Royalty Series circuit page showing team cards with seed, series record, game record and differential
royalty.meistyr.tech/circuits — standings
Royalty Series standings table, sortable by seed, series record, game record and differential
Royalty Series match-day view on a phone, listing weekly matchups with team logos and series scores
engagement by device
Desktop49% of users
6m 18s
Mobile49% of users
1m 52s
Users split almost evenly between desktop and mobile, but desktop engagement ran more than three times longer.
03 — architecture

How the system was set up

production architecture

Select a part to see what it did and how it connected.

Ubuntu · PM2 · Cloudflare · DigitalOcean
04 — decisions

Technical decisions

Auth required by default

Every route needed a valid token unless it was marked @Public(), and only login, token refresh and the health check were. A new endpoint was protected automatically. The API also checked roles, so the website’s key could only read, and each token carried a version number, so logging out invalidated all of that account’s tokens.

Discord IDs stored as strings

Discord IDs are 64-bit numbers. JavaScript rounds numbers above 2⁵³, and a rounded ID points to the wrong record. The API only accepted IDs as strings of digits, queries returned ID columns as text, and a global interceptor converted any BigInt values to strings.

Imports could be re-run safely

Results and stats came in as bulk imports. Each import used INSERT … ON DUPLICATE KEY UPDATE, so sending the same week again updated the existing rows instead of creating duplicates.

Roster changes checked before saving

Roster changes with several steps ran in a transaction. For a captain swap, the API demoted the old captain, promoted the new one, then checked the team had exactly one captain. If not, the whole change was rolled back.

royalty.meistyr.tech/circuits — stats
Royalty Series circuit page on the Stats tab: a sortable table of player rating, games, MVPs, score, goals, assists, shots and saves
05 — the migration

Moving from Express to NestJS

The API started out in Express and was rebuilt in NestJS with the same 54 routes, so the bot and website could switch over without changes. The rebuild split the code into modules by domain so it could grow, added validation and types to every request, and tightened authentication. Before launch, the new API ran in dev on the current season’s data alongside the Express API, and it replaced Express between seasons. An AI agent (Codex) wrote the tests with human guidance, and every issue it found was checked by hand before it was fixed.

06 — the archive

Keeping the site up after shutdown

When the platform shut down, the API and database went offline but the site stayed up. I saved the API’s responses and removed anything that linked a player’s name to an outside account, like tracker links with Steam and Epic IDs and Discord avatars with user IDs. I also copied the team and sponsor logos off the CDN. The site’s server routes now serve that saved data in the same format the API used, so no pages or components had to change. That’s the live demo linked above.

royalty.meistyr.tech
The archived Royalty Series homepage: the Royalty Series wordmark, the headline Competition for the crown, and the Season VII welcome banner
07 — lessons learned

What I’d do differently

A lot of the league’s work happened outside the platform, in the Discord bot and Google Sheets. On Car Soccer City I’m building those tools into the platform itself, with admin screens in the web app and API endpoints to support them.

next

Threat Intelligence Ingestion Pipeline

Security engineering: PDF, Excel, CSV and JSON intake, normalization, and detection-ready content in a SIEM.