Motivation
Every engineering portfolio needs a project that goes beyond CRUD. Something that deals with real-time data, concurrent consumers, cache invalidation, and the kind of infrastructure decisions that come up in production systems.
I wanted to build something I genuinely cared about. Not a todo app, not a Twitter clone, but a system that processes live events, updates multiple data stores in parallel, and pushes changes to users in real time. Football was the perfect domain.
Architecture Overview
Minute93 follows an event-driven architecture with a clear separation between data ingestion, processing, and delivery. Here's the high-level view:
Each Kafka consumer is independent. If one fails, the others continue processing. This gives us resilience without complexity. The poller writes to Kafka, and everything downstream is eventually consistent.
The Event Pipeline
The heart of Minute93 is its event pipeline. A poller worker hits the API-Football endpoints on a configurable interval, deduplicates events using Redis Sets, and publishes new events to Kafka.
Real-Time Delivery
Getting data from Kafka to the browser requires bridging two worlds: the backend event stream and the frontend. I chose Server-Sent Events (SSE) over WebSockets for several reasons:
- Unidirectional: the server pushes, the client listens. Perfect for live scores.
- Auto-reconnects: built into the EventSource API, no library needed.
- No protocol upgrade: works over standard HTTP, friendly to proxies and load balancers.
- Simpler to debug: it's just a long-lived HTTP response with text/event-stream content type.
The SsePublisher Kafka consumer receives events and publishes them to a Redis Pub/Sub channel. The NestJS SSE controller subscribes to that channel and streams events to connected browsers.
Data Layer
The data layer uses PostgreSQL for durable storage and Redis for hot reads. Standings and top scorer rankings are computed as materialized views, refreshed concurrently after each batch of events.
Search is powered by PostgreSQL's pg_trgm extension, trigram-based fuzzy matching that tolerates misspellings without needing Elasticsearch. A GIN index on player and team names keeps it fast.
Lessons Learned
Building Minute93 taught me several things that don't show up in architecture diagrams:
What's Next
Minute93 is a living project. Here's what's on the roadmap:
- Load testing with k6 during live Champions League matchdays
- Adding match prediction models using historical data
- Mobile-optimized PWA with push notifications
- Grafana dashboards for real-time system health monitoring
The goal is to run this system live during the Champions League 2025-26 season and write about what happens when real traffic hits a distributed system I built from scratch.