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 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:
To ensure scoreably’s long-term success, I needed a more robust, scalable, and maintainable architecture – enter scoreably v2.
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:
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.
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).
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.
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.
This transition wasn’t just about using new tools – it was about building a better foundation:
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!