BLOGDEVLOG

How We Built HackPortal in 3 Weeks

AC
Alex ChenPresident · DevSoc
12 MAR 20258 min read
next.jsopen sourcehackathon

From napkin sketch to production: a behind-the-scenes look at how our team shipped an open-source hackathon platform in 21 days — what worked, what didn't, and what we'd do differently.

The Problem We Were Solving

Every year DevSoc ran a hackathon and every year we cobbled together a registration system from Google Forms, a spreadsheet for teams, and a separate judging spreadsheet. It worked — barely — but it meant the organisers were spending the first six hours of the event manually matching people into teams and emailing PDFs to judges.

We needed something purpose-built: a single platform where participants register, form teams, submit projects, and judges score submissions — all in real time. Nothing off-the-shelf quite fit a small university society's needs and budget, so we decided to build our own and open-source it so other societies could use it too.

Choosing the Stack

We had three weeks and a team of four. That meant every architectural decision needed to minimise friction. We landed on Next.js 14 with the App Router for the frontend — our team already knew React, the file-based routing removes a class of decisions, and server components meant we could avoid over-fetching on the dashboard views.

For the backend we chose Supabase: it gave us a PostgreSQL database, real-time subscriptions (critical for the live leaderboard), row-level security, and auth — all with a generous free tier. The alternative was building a REST API from scratch, which would have consumed a week on its own.

typescript
// Registration form type — strict from day one
interface RegistrationPayload {
  firstName: string;
  lastName: string;
  email: string;
  university: string;
  yearOfStudy: 1 | 2 | 3 | 4 | "postgrad";
  dietaryRequirements?: string;
  teamCode?: string; // optional — can join later
}

Week 1: Scaffolding & Auth

Week one was all about foundations. We set up the monorepo, configured CI with GitHub Actions (lint, typecheck, Playwright smoke tests), and got auth working with Supabase's email magic link. Magic links were a deliberate choice — no passwords to forget, no OAuth complexity, and participants only need to log in once.

The biggest time sink was row-level security policies. Getting Supabase RLS right is non-trivial when you have three user roles (participant, organiser, judge) and complex team-level permissions. We ended up pairing on it for a full afternoon, but the investment paid off — we never had a security incident or data leak across the entire event.

Week 2: Registration & Teams

Week two built the core participant journey: registration form, email confirmation, team creation, and the team invite system. The trickiest part was team invites — we wanted participants to be able to share a six-character code and have their friends join instantly without a second email confirmation round.

We shipped a real-time team roster using Supabase's PostgreSQL changes subscription. When someone joins your team, everyone already on the team sees the roster update live. It sounds simple but it was the feature participants talked about most on the day — small UX touches like that matter more than you expect.

Week 3: Judging & Leaderboard

The judging interface was week three's main task. Judges needed to score projects across four criteria, leave notes, and see an aggregated ranking. We built a simple scoring rubric component and a live leaderboard that updated as judges submitted scores. Twenty-four hours before the event, we found a bug where the average score calculation broke on ties — fixed it with a tense midnight PR.

Deployment was on Vercel with a Supabase project in the same region. Zero-downtime deploys meant we could push patches during the event without participants noticing. We ended up pushing three small fixes during the 48 hours — that ability to iterate in production was invaluable.

typescript
// Aggregating judge scores — the bug was here
function aggregateScores(scores: JudgeScore[]): number {
  if (scores.length === 0) return 0;
  const total = scores.reduce((sum, s) =>
    sum + s.innovation + s.technical + s.design + s.impact, 0
  );
  // Was missing division by criteria count — fixed
  return total / (scores.length * 4);
}

What We Actually Shipped

In 21 days we shipped: participant registration with email confirmation, team creation and invite codes, a project submission form with GitHub link and demo URL, a judge scoring interface with four criteria, a live public leaderboard, and an organiser dashboard for managing participants and triggering announcements.

The event ran with 200 registrations, 47 teams, and 12 judges. The platform handled the load without breaking a sweat. Post-event survey: 94% of participants rated the registration experience as 'smooth' or 'very smooth'. That's the number we're most proud of.

Lessons Learned

Three things we'd do differently. First: wire up auth in day one, not day three. Everything downstream depends on knowing who the user is and what role they have — delaying auth created awkward refactors in week two. Second: write integration tests for the real-time features earlier. Unit tests alone don't catch subscription race conditions.

Third, and most importantly: talk to your users before you build. We assumed judges wanted a complex rubric — they actually wanted a simple 1–10 score and a text field. We over-engineered the scoring UI and had to simplify it in week three. The best features came from a 20-minute conversation, not a whiteboard session.

AC
Alex ChenPresident · DevSoc

3rd year CS student, open source enthusiast, and fullstack developer. Leads the HackPortal project team.

Rotating vector

JOIN US

Privacy policy

Cookie policy

Terms & Conditions

2024 DevSoc