This is a submission for the DEV Weekend Challenge: Community.
The Community
Canoe and kayak slalom has a strong grassroots scene: small clubs, volunteer judges, parents on the riverbank, and athletes who travel long distances for a single race run. Yet many local events still run on paper start lists, handwritten penalties, and spreadsheets that never leave the timing laptop.
I built Canoe Slalom Live for that community: local organizers who want something lightweight they can run on a single laptop at the course, judges who need a simple way to record runs, commentators who want quick access to athlete stories, and spectators who just want to understand what is happening on the water in real time.
What I Built
Canoe Slalom Live is a tiny live-timing and results web app for canoe/water slalom competitions. It runs as a Go web server with an embedded SQLite database and server-rendered HTML, so an organizer can start it on a laptop at the riverside and let everyone else follow the event from their phones.
The app provides a public event page with a start list grouped by category, a live leaderboard with penalties, a photo gallery, dedicated commentator and athlete profile views, plus a mobile-friendly judge panel for entering runs and penalties. It focuses on making results understandable for spectators while giving judges and commentators tools that fit how small events actually work.
It’s a single Go + SQLite binary that can run on a small VPS or services like Google Cloud Run.
Demo
GitHub repository (code + README + screenshots):
AcaciaMan
/
canoe-slalom-live
Weekend-built sports app for canoe/water slalom meets. Supports creating events, managing start lists and athlete bios, recording runs and penalties, and showing a live leaderboard for the whole community.
🛶 Canoe Slalom Live
Live timing and results for canoe/water slalom competitions. Built for grassroots events — run it on a laptop at the riverside, let spectators follow on their phones.
Short introduction video https://github.com/user-attachments/assets/90fe481d-032e-493f-acd4-5c0e1be7cca1
Quick Start
# Prerequisites: Go 1.22+, GCC (for SQLite CGo driver)
go mod tidy
go run main.go -seed
# Opens at http://localhost:8080
# Judge panel at http://localhost:8080/judge/events/demo-slalom-2026
To enable authentication for judge routes:
# Windows
set ADMIN_TOKEN=secret123
go run main.go -seed
# Linux/macOS
ADMIN_TOKEN=secret123 go run main.go -seed
# Judge panel: http://localhost:8080/judge/events/demo-slalom-2026?token=secret123
Stack
-
Go —
net/httpwith Go 1.22+ pattern routing,html/templatefor server-rendered HTML -
SQLite — embedded via
github.com/mattn/go-sqlite3(CGo), WAL mode, singledata.dbfile - Vanilla HTML/CSS/JS — no build step, no framework, no bundler
Pages
| Route | Page | Description |
|---|---|---|
/events/{slug} |
Event | Start list grouped by category with athlete bios |
/events/{slug}/leaderboard |
Leaderboard | Live rankings with penalty sparklines and auto-refresh |
/events/{slug}/photos |
Photo Gallery | Grid of event |
Short intro video (live walk-through of the seeded demo event):
https://github.com/user-attachments/assets/90fe481d-032e-493f-acd4-5c0e1be7cca1
To try it locally:
bash
go mod tidy
go run main.go -seed
# Opens at http://localhost:8080
# Judge panel at http://localhost:8080/judge/events/demo-slalom-2026
Code
The full source code is available here:
AcaciaMan
/
canoe-slalom-live
Weekend-built sports app for canoe/water slalom meets. Supports creating events, managing start lists and athlete bios, recording runs and penalties, and showing a live leaderboard for the whole community.
🛶 Canoe Slalom Live
Live timing and results for canoe/water slalom competitions. Built for grassroots events — run it on a laptop at the riverside, let spectators follow on their phones.
Short introduction video https://github.com/user-attachments/assets/90fe481d-032e-493f-acd4-5c0e1be7cca1
Quick Start
# Prerequisites: Go 1.22+, GCC (for SQLite CGo driver)
go mod tidy
go run main.go -seed
# Opens at http://localhost:8080
# Judge panel at http://localhost:8080/judge/events/demo-slalom-2026
To enable authentication for judge routes:
# Windows
set ADMIN_TOKEN=secret123
go run main.go -seed
# Linux/macOS
ADMIN_TOKEN=secret123 go run main.go -seed
# Judge panel: http://localhost:8080/judge/events/demo-slalom-2026?token=secret123
Stack
-
Go —
net/httpwith Go 1.22+ pattern routing,html/templatefor server-rendered HTML -
SQLite — embedded via
github.com/mattn/go-sqlite3(CGo), WAL mode, singledata.dbfile - Vanilla HTML/CSS/JS — no build step, no framework, no bundler
Pages
Route
Page
Description
/events/{slug}
Event
Start list grouped by category with athlete bios
/events/{slug}/leaderboard
Leaderboard
Live rankings with penalty sparklines and auto-refresh
/events/{slug}/photos
Photo Gallery
Grid of event
Key routes:
- /events/{slug} – event overview with start list and athlete bios
- /events/{slug}/leaderboard – live leaderboard with auto-refresh and penalty sparklines
- /events/{slug}/photos – photo gallery with photographer credits
- /events/{slug}/commentator – big-screen commentator view
- /events/{slug}/athletes/{id} – athlete profile with run history and linked photos
- /judge/events/{slug} – judge panel for recording runs (auth-protected via admin token)
How I Built It
Stack and architecture
I used Go 1.22 with net/http pattern routing and html/template for fully server-rendered pages, backed by a single SQLite database file accessed through github.com/mattn/go-sqlite3. There is no build pipeline or frontend framework: just vanilla HTML, CSS, and a tiny bit of JavaScript for auto-refresh.
The project is split into small packages: domain for core types (events, athletes, runs, sponsors, photos), store for SQLite queries, and handler for public and judge HTTP handlers plus authentication and logging middleware. Templates live in templates/ and are composed around a shared layout with a simple nav bar linking Event, Leaderboard, Photos, and Judge.
Data model and scoring
The SQLite schema has seven tables: events, categories, athletes, entries, runs, sponsors, and photos. Entries link athletes to an event and category with a bib number; runs store raw time, touches, missed gates, and computed total time, and a UNIQUE(entry_id, run_number) constraint prevents duplicate runs. Scoring follows standard ICF rules: +2 seconds per gate touch, +50 seconds per missed gate, ranking athletes by their best single-run total time.
UX for each role
For spectators, the leaderboard auto-refreshes every 10 seconds and highlights medals, “NEW” runs, time-behind-leader, and tiny CSS sparklines that compare raw time vs penalties. Commentators get a dedicated view that shows the latest result in huge type with bio and penalties plus current top three for each category, optimized for projection. Judges use a mobile-friendly panel with large tap targets, penalty steppers, confirmation before saving, recent runs, and run-status indicators so they can work quickly in a noisy outdoor environment.
Safety and polish
To keep things safe without overbuilding auth, judge routes are protected with an ADMIN_TOKEN environment variable and a simple session cookie once the token is used. There is server-side validation for time ranges and maximum penalties, basic security headers, and structured request logging with method, path, status, and duration. I also seeded a complete demo event (Demo Slalom 2026) so anyone cloning the repo can see realistic data, categories, and sponsors immediately
Top comments (0)