Project Overview & FastAPI + Redis Setup

Premier League Predictor: FastAPI & Redis

Chapter 1 · Project Overview & FastAPI + Redis Setup

The Shared Spec

This app tracks weekly predictions for every fixture across a real 38-gameweek Premier League season, scoring four genuinely different prediction sources against what actually happened:

  • The user's own prediction — entered by hand each week
  • The BBC's expert (currently Chris Sutton) — their own published scoreline pick
  • The week's guest(s) — genuinely variable; some weeks have more than one guest, whose individual scores are averaged into a single "guest" figure for that fixture
  • The BBC's own AI prediction — manually transcribed each week from the BBC's own published article, not scraped or generated by this app

Scoring uses the real point values confirmed directly by the user during this project's own reference implementation, back in Premier League Predictor: FastAPI & PostgreSQL's own Chapter 6: 40 points for a correct score (the exact scoreline matched) and 10 points for a correct result (the W/D/L outcome matched even if the scoreline didn't). A real league table (3/1/0 points, goal difference, won/drawn/lost) sits alongside a second, parallel prediction league table ranking how each of the four real sources is actually performing across the season. The season itself holds 20 teams across 38 gameweeks, with the bottom 3 auto-calculated for relegation and the 3 promoted teams entered by hand. The fixture-entry UI stays deliberately fast — paired click targets and 20 team buttons, not a dropdown-heavy form repeated ten times a week.

This Variant's Own Real Angle

The already-completed Premier League Predictor: FastAPI & PostgreSQL course built this exact spec as a real, ordinary relational schema — teams, fixtures, and predictions as tables, joined and aggregated with real SQL. This course asks a genuinely different question: what happens if the database underneath it is Redis instead?

Redis isn't a relational database with SQL removed — it's a fundamentally different kind of store, built around a small set of real data structures (strings, hashes, lists, sets, and — the one this course leans on hardest — sorted sets) rather than tables and rows. A sorted set stores members each tagged with a real numeric score, kept continuously ordered by that score, with real O(log n) insertion and real O(log n + m) range reads. That's not a workaround for a league table — it's close to the data structure a league table actually is.

An Honest Scope Note — Read This Before Continuing
This is deliberately the narrower of the two FastAPI-backed variants in this project. Redis genuinely excels at the two things this course leans on it for — a live, continuously ordered league table and a live, continuously ordered prediction leaderboard — but it was never designed as a general-purpose store for rich relational history. The full PostgreSQL variant can run an arbitrary SQL query across every fixture and every prediction ever recorded; this course's own real answer to that same kind of question is honestly weaker, and Chapter 9 is dedicated entirely to working through exactly where that weakness shows up, with real, concrete examples rather than a vague disclaimer. The goal here isn't "prove Redis can do everything" — it's "find out, honestly, where it genuinely fits and where it genuinely doesn't."

Real Stack Setup: FastAPI + redis-py

redis-py — installed as the plain redis package — is the real, official Python client for Redis. Its own async support used to live in a separate package, aioredis; that project is now archived, with its functionality merged directly into redis-py itself as redis.asyncio, confirmed directly against Redis's own current client documentation.

$ pip install fastapi uvicorn redis
A Real, Dated Divergence From Redis's Own Official Tutorial
Redis's own official FastAPI tutorial — a genuinely current article, published and updated this year — still wires its Redis client up using FastAPI's older @app.on_event('startup') hook. Checked directly against FastAPI's own current documentation, that decorator is real but explicitly deprecated: FastAPI's own docs now recommend the lifespan async context manager instead, and are direct about why — startup and shutdown code sharing one function and one scope, rather than two separately registered handlers. This chapter uses the current recommended pattern, not the one shown in even a freshly-published official tutorial — a genuine reminder that "recently published" and "reflects the current recommendation" aren't always the same thing.

Building this app's own connection lifecycle the current way:

# main.py from contextlib import asynccontextmanager from fastapi import FastAPI import redis.asyncio as redis @asynccontextmanager async def lifespan(app: FastAPI): app.state.redis = redis.Redis(host="localhost", port=6379, decode_responses=True) yield await app.state.redis.aclose() app = FastAPI(lifespan=lifespan) @app.get("/health") async def health(): pong = await app.state.redis.ping() return {"status": "ok", "redis_ping": pong}

Run against a real, local Redis instance and confirmed with a real request:

$ curl http://localhost:8000/health {"status":"ok","redis_ping":true}

A Real Preview of the Primitive This Course Is Built Around

Chapter 2 builds the full real data model on top of Redis's own sorted sets — this chapter closes with a small, genuine taste of the exact command shape the rest of the course leans on, so the real payoff isn't abstract:

@app.get("/sorted-set-demo") async def sorted_set_demo(): r = app.state.redis await r.delete("demo:table") await r.zadd("demo:table", {"Arsenal": 78, "Chelsea": 71, "Liverpool": 82}) top = await r.zrange("demo:table", 0, -1, desc=True, withscores=True) return {"top_teams": top}
$ curl http://localhost:8000/sorted-set-demo {"top_teams":[["Liverpool",82.0],["Arsenal",78.0],["Chelsea",71.0]]}
Already Ordered, With Zero Sorting Code
Three teams were added in an arbitrary order — Arsenal, Chelsea, Liverpool — and the real response came back already sorted by points, highest first, with no ORDER BY, no application-side .sort() call, and no separate query at all. This is the real, structural case for choosing Redis for a live league table: the ordering isn't computed on read, it's maintained continuously as members are added, exactly the property a table that updates every time a result is entered actually wants.
Worth Knowing Now, Built On Properly Later
That same ordering guarantee has a real, sharp edge a Premier League table can't ignore: Redis resolves tied scores by the member's own name, not by anything meaningful like goal difference. Real Premier League points totals tie constantly. This chapter doesn't build the fix — Chapter 7's own league-table implementation does — but it's worth knowing from the very first sorted set this course creates, not discovering by surprise later.

Hands-On Exercises

Exercise 1

Point this chapter's own /health endpoint at a port with no Redis instance listening at all, and run it for real. Report the exact real exception type and message you get back, and explain what the current /health endpoint's own code would actually do with it (hint: nothing catches it).

📄 View solution
Exercise 2

Reproduce this chapter's own tie-breaking warning with real code: add three teams to a sorted set with the exact same score, read them back with ZRANGE in descending order, and explain the real order you get by working out what Redis's own tie-breaking rule (ascending by member name) would produce once the whole set is reversed for a descending read.

📄 View solution
Exercise 3

In your own words, explain why this course frames itself as "deliberately narrower" than its own PostgreSQL sibling, rather than simply "a different but equally capable" implementation of the same spec. What real question is this course trying to honestly answer that a course confident Redis can do everything wouldn't actually be answering?

📄 View solution

Chapter 1 Quick Reference

  • Same spec, different store — the identical four-source prediction/scoring system already built once on PostgreSQL, now built on Redis
  • redis.asyncio — the real, current async client, merged from the now-archived aioredis package
  • lifespan, not on_event — the real current FastAPI pattern, diverging deliberately from even a freshly-published official Redis tutorial
  • Sorted sets stay ordered on write — verified: no sort step needed on read, the real reason this course reaches for Redis at all
  • An honest, upfront limitation — tied scores break by member name, not by anything meaningful; Chapter 7 builds the real fix