Leveling Up scoreably: From Hacking Week MVP to Scalable Architecture

Leveling Up scoreably: From Hacking Week MVP to Scalable Architecture

In my previous blog post, I shared the story of how scoreably, our internal EAFC game tracking and rating app, grew from a simple flipchart into a beloved (and fiercely competitive) part of our company culture. What started as a fun hacking week project quickly gained traction, fueled by user feedback (mostly demands!) from our toughest critics: our own developers.
 The initial version of scoreably, hacked together quickly to solve the "who's really the best?" question using an Elo rating system, served its purpose beautifully. It proved the concept, digitized our game history, and added a whole new layer of intrigue to our office matches. Built using tools optimized for rapid prototyping (Supabase and Next.js), it allowed me to get a working version up and running fast.

However, as its popularity grew and feature requests demands piled up, I started noticing that I was hitting the limitations inherent in its initial design.

The Growing Pains of a Prototype

The same qualities that made the initial stack great for a hackathon—speed and simplicity—soon became challenges. My original setup relied heavily on Supabase for backend service and Next.js for the frontend, providing a quick, integrated development experience. However, as I pushed beyond the initial prototype, several issues emerged:

  • No Version Control for Database Changes: Supabase made it easy to define PostgreSQL views and functions through its web interface, but this came at the cost of version control. Without a local Supabase instance, every change had to be tested in production – far from ideal.
  • Tightly Coupled Features and Services: The Discord bot integration, while a fun addition, was deeply intertwined with the core application. This made extending or modifying the system unnecessarily complex, as changes often had unintended side effects.
  • Lack of a Dedicated API: Since Scoreably was initially built as a tightly integrated full-stack app, there was no clean separation between frontend and backend logic. As a Flutter agency, I recognized that a dedicated API would be crucial if I ever wanted to build a mobile app.
  • No Tests for Critical Logic: The Elo rating system worked well in theory, but with no test coverage, errors in calculation were difficult to catch before they affected real data.
  • Authentication & Organization Isolation Issues: As I moved towards making scoreably a public-facing product, I needed a way to isolate data between different organizations (i.e., clubs or groups of users). Supabase's default authentication model didn’t support this level of separation, requiring a major rethink.

To ensure scoreably’s long-term success, I needed a more robust, scalable, and maintainable architecture – enter scoreably v2.

Re-architecting for the Future: The scoreably v2 Foundation

My main goal for the rebuild? Separation of concerns. I wanted distinct parts of the application that could be worked on, tested, and deployed independently, all tied together with the safety net of TypeScript. This meant moving away from the all-in-one approach to something more structured:

1. Taming the Chaos with a Monorepo (PNPM Workspaces & Turborepo)

First things first, I wrangled the code into a monorepo using PNPM. Now, the backend code, the frontend code, and any shared bits (like TypeScript types – super important!) live happily in the same repository. This makes managing dependencies way easier and keeps things consistent, even though the frontend and backend are now separate projects. Additionally, Turborepo makes managing internal package dependencies as well as running a local dev server from a single command a breeze.

2. Giving the Backend its Own Space (scoreably-api)

scoreably needed its own dedicated engine room. For the API, I started with a lightweight Node.js server based on Hono.js - an incredibly fast web application framework with a lot of stuff already included, like middlewares and helpers for common features. But the real gamechanger is their built-in RPC client – I just give it my app definition and have a fully type-safe client to use in my frontend.
In the database layer, I decided to stay mainstream with PostgreSQL. Does everything nicely and the views are pretty helpful for hiding away complex queries, like aggregating user statistics. To talk to it, I chose DrizzleORM. No need to learn a new schema language (looking at you, Prisma), fast and has its own data explorer built in, Drizzle Studio. And it's also fully type-safe (I think you can start to see a pattern).
One of the most important aspects for me was choosing a reliable auth provider that doesn't empty my wallet for basic features. I initially explored pre-built (but self-hosted) auth providers like WorkOS' AuthKit or Logto, but I liked the idea of being able to house it directly in my codebase – that's where Better Auth comes in. Easy to set up, loads of included features and third-party plugins and it also runs on DrizzleORM, so I can easily query my auth schema the same way I query the rest of my data.
Other honorable mentions include Zod for rock-solid data validation, Pino for efficient logging and Vitest for easy testing. To level up my testing game and have the test suite fully self-contained, I also integrated a Postgres TestContainer. This allows me to spin up a fresh database instance for each test run (and I can configure it in TypeScript).

3. Crafting a Modern Frontend (scoreably-frontend)

With the backend sorted, the user interface got its own dedicated rebuild using the full-stack capabilities of TanStack Start. I was tempted to use Next.js again, but TanStack Start (yes, I've seen the big "beta" label - sue me) was just too tempting. And I did not regret it – fully type safe routing and pristine integration with TanStack Query, which makes data fetching, server state and caching almost effortless.
Now to the actual thing that my users see – the UI. I'll be honest – I'm a developer, not a designer. However since I built the prototype, I fell in love with shadcn/ui. Although it doesn't give me the most distinguishable design, it provides me with a solid visual foundation that I can build on. And the pre-built components and graphs are a life-saver in many aspects. Forms are handled robustly with React Hook Form, and tying it together with Zod for validation (often reusing the same validation rules defined way back in the shared backend code) keeps everything consistent and type-safe.

4. Professionalized DevOps

Initially I contemplated moving everything to the cloud (AWS or GCP), however I quite enjoyed my experience with coolify, a self-hosted alternative to proprietary platforms like Vercel. However it being based on PHP, I had some resentments and opted to try out Dokploy for v2, which does essentially the same, just with TypeScript and some nifty Docker Swarm features.
Both the API and frontend apps are built from Dockerfiles, leveraging multi-stage builds to keep the builds as fast as possible. The images are built in a GitHub action and pushed to the GitHub container registry. Dokploy then simply downloads the latest images and automatically deploys them with zero downtime.

Bonus: Secret Management: While there's just 3 environments for this app (test, local and production), I've always hated managing .env files. While browsing through the Dokploy catalog, I stumpled upon Infisical - an open-source secrets management platform. Now I can manage all environments in one place and don't have to worry about missing variables.

Key Benefits of the New Architecture

This transition wasn’t just about using new tools – it was about building a better foundation:

  • Enhanced Scalability: The separate API and frontend can be scaled independently.
  • Improved Maintainability: Clear boundaries between frontend, backend, and database logic make the codebase easier to understand, test, and modify.
  • True End-to-End Type Safety: I've been mentioning a lot, but this is truly the most beautiful aspect of them all – from database schema (Drizzle) through the API (Hono/Zod) to the frontend (Hono RPC client/TanStack Router/Query/React Hook Form), TypeScript ensures correctness. The auto-generated API client guarantees the frontend always consumes the API correctly.
  • Increased Performance: Optimized components (Hono, Vinxi) and dedicated services improve responsiveness.
  • Better Developer Experience: Faster builds, hot reloading, type safety, and a clear structure enhance productivity.
  • Greater Flexibility: I now have the control needed to implement more complex features and integrations.

Ready for the Next Match

And that wraps up the deep dive into scoreably v2's architecture! It's been quite the journey evolving from that initial hacking week buzz to the setup we have today.

The real win here isn't just the shiny new tech stack, though – it's the confidence that comes with it. Having a solid, testable, and scalable foundation under scoreably makes development feel less like hacking and more like engineering. It proves that investing in good architecture, even for our internal fun-and-games projects, genuinely pays off in the long run by making things easier to manage and grow.

With this rebuild complete, scoreably feels ready for whatever the future holds – more features, more users (within the company, for now!), and certainly more intense EAFC rivalries. The foundation is set, the system is humming, and honestly, I'm just excited to start building on top of it.

Want to work with cutting-edge technologies like these and maybe even battle your way onto the scoreably leaderboard? Check out our careers page!

Image of Lukas
by Lukas
on 05.06.2025
Lukas loves diving into tech, obsessing over details, and uncovering clean, best-practice solutions. He's currently leading our FIFA ranking—tracked, of course, by the tool he built himself. What a coincidence!
#insights #reactjs #nodejs

We love to talk. Let´s make your projects remarqable!