Project Overview & Astro + a Thin API Backend Setup

Premier League Predictor: Astro

Chapter 1 · Project Overview & Astro + a Thin API Backend Setup

This is the fourth course built in the Premier League Predictor set — after Premier League Predictor: FastAPI & PostgreSQL (the full relational reference implementation) and Premier League Predictor: Django & MySQL, both complete, with Premier League Predictor: FastAPI & Redis still outlined. This variant takes a genuinely different angle from all three: rather than standing up a separate backend framework and API service, it builds the entire app — pages and data layer alike — inside a single real Astro project, using Astro's own native server endpoints instead of a separate service. That makes this the most direct "how would this actually live on my real site" story of the whole set, since the site this predictor is meant to eventually join is itself already built in Astro.

What the App Actually Does

The same shared spec every course in this set builds toward — a personal, weekly Premier League prediction tracker, not a generic sports app:

  • Track every fixture across a real 38-gameweek Premier League season, 20 teams, 10 fixtures per gameweek.
  • Record four real prediction sources per fixture:
    • the user's own prediction,
    • the BBC's expert prediction (currently Chris Sutton),
    • that week's guest predictor(s) — genuinely variable; some weeks have more than one guest, whose scores get averaged into a single "guest" figure for that week,
    • and the BBC's own published AI-generated prediction, manually transcribed each week rather than scraped or generated by this app.
  • Score two different things per prediction once a result is in — a correct score (the exact scoreline matched) and a correct result (the predicted win/draw/loss outcome matched, even if the scoreline itself was wrong).
  • Maintain the real league table — points (3/1/0), goal difference, wins/draws/losses — alongside a second, parallel prediction league table ranking how each of the four real predictors is actually doing across the season.
  • Handle promotion and relegation between seasons — the bottom three teams are auto-calculated and removed at season end; the three promoted teams are entered manually, since which teams come up from the Championship isn't something this app tracks on its own.

Fixture entry is deliberately fast to use, not a generic form. Ten fixtures a week, every single week, for 38 weeks — a dropdown-heavy "select home team, select away team" form for every one of them would be genuinely tedious. Chapter 4 builds a real click-to-pair interface instead: 20 clickable team buttons, paired directly into home/away boxes.

The point values are already settled
Unlike this course's own FastAPI sibling, which had to leave the real scoring numbers open in its own first chapter, this course starts with them already confirmed: a correct score is worth 40 points, and a correct result is worth 10 points — resolved directly with the user during the FastAPI course's own Chapter 6, and reused unchanged here so every variant in this set scores identically.

Why Astro for This One

Of the four variants in this set, this is the one built specifically to answer "could this genuinely just be a page on my real site" — and Astro's own real feature set makes that possible without reaching for a second framework at all:

  • Astro's own server endpoints are the backend. A file under src/pages/api/ exporting a GET or POST function is a real, working API route — there's no separate Express server, no separate FastAPI process, nothing extra to deploy alongside the site itself.
  • A single SQLite file, not a database server. A personal hobby project run by one person doesn't need a standalone PostgreSQL or MySQL server sitting on standby — better-sqlite3 reads and writes one plain file on disk, which is exactly the amount of database this app actually needs.
  • Per-page rendering control, decided route by route. Astro can serve some pages statically and others on demand from the very same project, which turns out to matter a great deal for a data-driven app like this one — covered in real depth in Chapter 10.
This Course Assumes Real Astro Groundwork Already Covered Elsewhere
This site's own astro1 (Astro) course already covers Astro's own component model, file-based routing, and static-first rendering philosophy in real depth, and website-rebuild-with-astro1 covers a full real Astro site rebuild. This course doesn't re-teach any of that — it assumes it, and moves straight into this predictor's own real schema, endpoints, and features.

Setting Up: Astro, Server Endpoints & SQLite

A local Node.js install is assumed from here on. Scaffold a fresh Astro project first:

# Scaffold a new Astro project (choose the empty template, TypeScript "Strict") npm create astro@latest -- pl-predictor-astro cd pl-predictor-astro # Add the Node adapter, needed for real server-rendered routes npx astro add node # Install a lightweight, file-based SQLite driver npm install better-sqlite3

Astro renders every page statically by default. Since this app needs real, on-demand server logic — API routes, an admin area that writes to the database — astro.config.mjs needs to switch the whole project over to server output first:

// astro.config.mjs import { defineConfig } from 'astro/config'; import node from '@astrojs/node'; export default defineConfig({ output: 'server', adapter: node({ mode: 'standalone' }), });

A minimal project structure, following this site's own established Astro project convention rather than inventing a new one:

pl-predictor-astro/ ├── astro.config.mjs ├── src/ │ ├── lib/ │ │ └── db.ts # better-sqlite3 connection + schema setup (Chapter 2) │ ├── pages/ │ │ ├── index.astro # the real league table (Chapter 7) │ │ ├── admin/ # team/fixture management (Chapter 3-4) │ │ └── api/ │ │ ├── health.ts │ │ ├── teams.ts │ │ ├── fixtures.ts │ │ └── predictions.ts │ └── components/ └── data/ └── pl_predictor.db # created on first run, gitignored

A minimal health-check endpoint, confirming the setup works before any real route exists:

// src/pages/api/health.ts import type { APIRoute } from 'astro'; export const GET: APIRoute = async () => { return new Response(JSON.stringify({ status: 'ok' }), { status: 200, headers: { 'Content-Type': 'application/json' }, }); };
npm run dev

Visiting http://localhost:4321/api/health should return {"status":"ok"} — a real, live server response coming straight out of the Astro project itself, with no second process running anywhere.

Every page defaults to on-demand now — that's a deliberate, temporary tradeoff
Setting output: 'server' makes every page in the project server-rendered on each request by default, not just the API routes — including pages that don't actually need it, like a mostly-static "About this tracker" page, if one ever gets added. That's the right tradeoff for this chapter, since almost every page this app needs really is dynamic. Chapter 10 comes back to this directly: Astro lets an individual page opt back into static prerendering with a single export const prerender = true line, and by then it'll be clear exactly which of this app's own pages can genuinely take advantage of that.

Where This Course Is Headed

A real SQLite schema for teams, seasons, gameweeks, and fixtures (Chapter 2); managing the 20 competing teams each season (Chapter 3); the fast click-to-pair fixture-entry UI (Chapter 4); recording all four prediction sources per fixture (Chapter 5); entering results and applying the confirmed 40/10 scoring (Chapter 6); the real league table (Chapter 7); the parallel prediction league table (Chapter 8); promotion and relegation between seasons (Chapter 9); Astro islands and exactly where client-side JavaScript actually earns its place in a static-first framework (Chapter 10); deployment (Chapter 11); and a capstone on mounting this predictor directly onto the real, live Astro site (Chapter 12).

Hands-On Exercises

Exercise 1

Explain the real difference between a "correct score" and a "correct result" prediction, and describe one real scenario where a prediction earns the 10-point correct-result score but not the 40-point correct-score bonus.

📄 View solution
Exercise 2

Explain why this app averages multiple guest predictors into a single "guest" figure for weeks with more than one guest, rather than tracking each guest as a permanently separate predictor across the season.

📄 View solution
Exercise 3

Set up the Astro project and the /api/health route yourself, confirm it responds with a real server request, then explain — in your own words — the real difference between a page that's server-rendered on every request under output: 'server' and one that opts back into static prerendering with export const prerender = true.

📄 View solution

Chapter 1 Quick Reference

  • The shared app — a weekly Premier League prediction tracker: 4 real prediction sources per fixture (user, BBC expert, guest(s) averaged, BBC AI), scored 40 points for a correct score and 10 for a correct result, across a real 38-gameweek season
  • Two league tables — the real Premier League table, plus a parallel prediction league table ranking predictor performance
  • Promotion/relegation — bottom 3 auto-calculated and removed; the 3 promoted teams entered manually
  • Why this variant — Astro's own native server endpoints as the entire backend, plus a single-file SQLite database — the most direct "bolt this onto my real site" story of the whole set
  • Config — output: 'server' with the @astrojs/node adapter, so every route is on-demand by default
  • Database — SQLite via better-sqlite3, a single gitignored file, no separate database server to run
  • Assumed groundwork — Astro fundamentals, covered in this site's own dedicated astro1 and website-rebuild-with-astro1 courses
  • Next chapter: Data Modeling — teams, seasons, gameweeks & fixtures, as a lightweight SQLite-backed API