Data Sources

About the Database & APIs

This dashboard is powered by one free public API and a Supabase database. Past seasons are served from the database for speed and reliability - the current season is always fetched live from Jolpica.

Data origin

The app uses a hybrid data strategy depending on the season selected. Historical seasons (2000–2025) are stored in a Supabase PostgreSQL database - they load in under a second with no rate limiting. The current live season (2026) is always fetched directly from the Jolpica F1 API to ensure real-time accuracy.

Past seasonsSupabase (2000 – 2025)
Current seasonJolpica F1 API (live)

Why a database?

Jolpica caps requests at 200 per hour and pages results at 30 rows. Loading a full 24-race season requires ~32 paginated requests. By seeding historical data into Supabase once, the app fetches an entire season in 5 parallel queries instead of 32+ sequential API calls - reducing load time from 4–5 seconds to under 1 second.


Supabase Database

Supabase is an open-source Firebase alternative built on PostgreSQL. It provides a REST API that can be queried directly from the browser using the anon public key - no backend server needed. Row Level Security (RLS) is enabled on all tables, restricting the anon role to read-only SELECT access.

ProviderSupabase (PostgreSQL)
AuthAnon public key
AccessRead-only (RLS enforced)
Coverage2000 – 2025
Seeded fromJolpica F1 API
Queries5 parallel per season

Database schema

TABLE
races

One row per race. Stores year, round, name, circuit, country, date, winner, team, grid, laps, and time. Unique constraint on (year, round).

TABLE
race_results

One row per driver per race. Stores year, round, pos, name, code, team, grid, and fastest_lap (boolean). Used to build allResults arrays for charts and drill-downs. Unique constraint on (year, round, pos).

TABLE
qualifying_results

One row per driver per qualifying session. Stores year, round, pos, name, code, team, and lap times q1, q2, q3 (nullable text). Unique constraint on (year, round, pos).

TABLE
driver_standings

Final driver championship standings per season. Stores year, pos, name, code, nat, team, points, and wins. The first row (pos 1) is used to derive all champion statistics. Unique constraint on (year, pos).

TABLE
constructor_standings

Final constructor championship standings per season. Stores year, pos, team, points, and wins. Used to build the constructor bar chart and right column rankings. Unique constraint on (year, pos).

Security

Row Level Security is enabled on all five tables. The anon public key - visible in the client-side code - can only execute SELECT statements. INSERT, UPDATE, and DELETE are blocked at the database policy level, so the data cannot be modified even if the key is extracted.


Jolpica F1 API

Jolpica is an open-source API that maintains backwards compatibility with the now-deprecated Ergast API. It covers every Formula 1 season from 1950 onwards - race results, qualifying, driver and constructor standings, lap times, pit stops, and more. No account or API key required.

In this app, Jolpica is used in two ways: as the original data source for seeding the Supabase database, and as the live data feed for the current season (2026) on every page load.

Base URLhttps://api.jolpi.ca/ergast/f1
AuthNone
Rate limit200 req / hour
FormatJSON
Coverage1950 → present
Page size30 rows (hard cap)

Endpoints used

GET
/{year}/results/?limit=30&offset=0

Race results for every round - paginated. Each page returns up to 30 driver-result rows (not races). A 24-race season has ~480 rows (24 × 20 drivers), requiring 16 paginated requests merged by round number.

GET
/{year}/driverstandings/

Final driver championship standings - position, points, wins, nationality, and team for every classified driver.

GET
/{year}/constructorstandings/

Final constructor championship standings - position, points, and wins for each team.

GET
/{year}/qualifying/?limit=30&offset=0

Qualifying results per round - Q1, Q2, Q3 times and grid positions. Also paginated by driver-result row.

Pagination note

The limit parameter is ignored for values above 30 - Jolpica always returns a maximum of 30 rows per page regardless. MRData.total tells you the total row count, not the race count. This dashboard paginates through all pages in parallel and merges results by round number to reconstruct complete race objects.


Planned features

The current data model covers race results, qualifying, driver and constructor standings - enough to tell the championship story for every season back to 2000. A number of deeper analytical features are under consideration for future releases, each requiring either schema additions or a new data source.

PLANNED
Lap time distribution

Per-race lap time box plots for the top finishers - medians, IQR, and outlier filtering. Requires seeding a lap_times table from Jolpica's /{year}/{round}/laps/ endpoint, or integrating OpenF1 for 2023+ seasons.

PLANNED
Pit stop telemetry

Total stops, average and fastest pit duration, and max stops per driver per race. Jolpica covers pit stop data from 2012 onwards via /{year}/{round}/pitstops/ - would be seeded into Supabase alongside existing race data.

PLANNED
Tyre strategy

Compound choices and stint lengths per driver per race. Available via OpenF1's /stints endpoint for 2023 onwards - would show how tyre strategy influenced race outcomes.

PLANNED
Sprint race results

Sprint weekend results and points separate from the main race. Currently excluded because sprint points require a separate /{year}/sprint/ endpoint call per round, which affects championship points accuracy for 2021+ seasons.

PLANNED
Live race timing

Real-time position tracking, gap to leader, and sector times during an active race weekend. Would use OpenF1's live endpoints - a meaningful step up in complexity requiring WebSocket or long-polling architecture.


How data flows

On season select, the app first checks an in-memory cache. If the data is cached it renders instantly. For past seasons (2000–2025) it fires 5 parallel Supabase queries. For the current season it fires 4 parallel Jolpica requests with paginated batching.

01

Season select

An AbortController cancels any in-flight requests from a previous selection. The app checks the in-memory cache first - if hit, renders immediately and skips all network calls.

02

Route to data source

Past seasons (≠ current year) query Supabase - 5 parallel SELECT statements covering races, results, qualifying, driver standings, and constructor standings. The current season fetches from Jolpica via Promise.all with paginated batching.

03

Normalise

Both paths produce an identical data shape - the same seasonData object with races, drivers, constructors, and qualifying arrays. All rendering code is shared regardless of the source.

04

Render

Hero, metrics, champion vs runner-up strip, tabs, right column, and charts are all rendered from the normalised data object. Adjacent seasons are pre-fetched silently in the background.

05

Cache

Season data is stored in memory - re-selecting a season skips all fetches and network calls. The current season busts the cache after 5 minutes to pick up live race updates.