Web AppQ2 2021Solo build
Iplheat
Every bit of IPL action — updates, videos and live scores in one destination.

The build
Live scores, updates and video in one place — and my first project built around somebody else's data.
Problem
Following a tournament means four tabs: one for the score, one for highlights, one for news, one for fantasy. Each reloads on its own schedule and none of them knows about the others. For a six-week tournament, that's a lot of friction for something people do every evening.
Constraints
- The data isn't mine. Scores came from a public API, on its schedule, with its rate limits and its outages. Everything about the app's freshness was bounded by someone else's service.
- Traffic is spiky by definition. Nothing for twenty-two hours, then everything at once when a match starts.
- Read-only. No accounts, no writes — which removed most of the usual problems and made the remaining one, caching, the whole project.
Approach
An Express server that fetches from the upstream score API, caches the response, and serves every visitor from that cache rather than proxying each request through:
// One upstream call per interval, however many visitors are watching.
let cache = { at: 0, data: null };
async function scores() {
if (Date.now() - cache.at < 30_000) return cache.data;
cache = { at: Date.now(), data: await fetchUpstream() };
return cache.data;
}Trivia and a fantasy corner sit alongside the feed — static content whose only job is to be worth staying on the page for during the twenty minutes between innings.
Tradeoffs
- A thirty-second cache over live polling. Every visitor sees a score up to thirty seconds old, and the upstream API sees two requests a minute instead of two thousand. For cricket, thirty seconds is inside the delay of the broadcast anyway.
- Server-side caching over client-side. One shared cache means the hundredth visitor is as cheap as the first. It also means a single process holding state, which doesn't survive being scaled horizontally — irrelevant at one instance, wrong the moment it isn't.
- No accounts. Removed auth, storage, and privacy entirely. It also removed any way to build the feature people actually want, which is a fantasy team that persists.
Outcome
It served a tournament: scores, updates, and enough around them to be worth opening. The lesson that stuck was about dependency — when the interesting data belongs to someone else, your product's real design constraint is their rate limit, and caching stops being an optimisation and becomes the architecture.
Decisions
- Caching upstream responses over proxying each request. Chosen because a free-tier API key does not survive a match-day crowd. Against it: an in-process cache is invisible state that breaks silently if the app is ever run as more than one process.
- A public API over scraping. Chosen for a stable contract and no legal grey area. Against it: the data is whatever the API offers, and the ball-by-ball detail I wanted wasn't in it.
- Static trivia and fantasy over a real fantasy game. Chosen to keep a read-only app read-only. Against it: it's the feature that would have brought people back daily, and it's the one that isn't there.
Stack
- Node.js
- Express.js
- JavaScript
- REST API
- Materialize CSS
Related projects

Lecture Lense
Turns any recording into searchable, timestamp-cited notes.

Chitter
A mini social app that lets users sign up, log in and post whatever is on their mind.

Heckfree
Users get a public profile that showcases all of their links in one place.