There was a game I played from time to time. League of Legends (LoL). Watching pro matches, one thing stood out: teams drafting bans and picks, strategizing, scheduling scrims. Regular players had no platform to do any of this.
In March 2021, I started building one with a colleague. 55L, short for 5vs5 League. It later became known as GGScrim.
The Service
55L had two components.
ggscrim.com was a team matching platform. Teams register and find opponents to schedule scrims. banpick.kr was a virtual ban/pick simulator. Users could simulate the same ban/pick sequence as pro matches.
The ban/pick service attracted over 10,000 users.
Tech Choices
The API server ran on PHP. I had prior experience building servers with PHP, and starting fast was the priority. I applied MVC with DI, Factory, and Singleton patterns, with Nginx as a reverse proxy.
Chat and notifications required real-time bidirectional communication. I set up a separate server with Node.js + Socket.io — HTTP request/response alone could not meet this requirement.
MariaDB handled data storage. Redis managed authentication tokens.
The client was a TypeScript + Lit SPA. Among Web Components frameworks at the time, Lit was the most concise and ran on standard APIs, which I believed would ensure long-term sustainability. I used Webpack for builds and Firebase for hosting.
The desktop app was built with Electron. It needed to communicate with the LoL client via sockets, and browsers cannot directly access local processes. Electron solved this constraint. It reused the web codebase and covered both Windows and Mac from a single codebase.
Mobile was handled through a PWA. It allowed installable delivery without a separate native app. I considered it the right way to secure mobile accessibility while reducing development cost.
Architecture
The overall system split into three areas.
Clients came in three forms: desktop (Electron), web (SPA), and mobile (PWA). Only the desktop app communicated directly with the LoL client via sockets. The rest were browser-based. All three connected to the same API server and Socket.io server.
On the server side, I separated the auth server from the API server. Authentication used JWT. Separating auth logic from business logic meant each could be modified and deployed independently. I covered the JWT auth structure and how it compares to session-based auth in a separate post.
The API server communicated with the Riot API to fetch champion and summoner data, storing it in MariaDB.
The Team
It started with two people. I handled all of the service architecture design, the JWT auth server, and the desktop app. I also covered most of the web client.
As the service grew, so did the team. Over five months, four people joined. Roles began to split, and the structure shifted from one person making every technical decision to a shared model.
Looking Back
It was an environment where every technical decision fell on me. I had to build my own rationale for each choice and face the trade-offs directly.
The Lit choice is one I would change if I did it again. I picked it for the longevity of Web Components standards, but considering ecosystem size and development speed, React would have been a better choice. At the time I prioritized the technology’s direction, but in an early-stage startup, speed matters more.
The project started from a game I played from time to time. I built a platform that did not exist, and the reference points I gained from that process still serve me in production work today.