Build a Fantasy Football App: Live Lineups, Player Stats & Real-Time Scoring

Build a fantasy football app with live lineups, player stats, and real-time scoring using GOAL API. The data model, the endpoints, and copy-paste code to power it.

GOAL API

4 min read
A fantasy football squad on a pitch with a live gameweek points counter and a leaderboard — build a fantasy football app with GOAL API

Every football season — and every major tournament — spawns a fresh wave of fantasy games. Official platforms, private leagues built for a group of friends, tournament-specific games that run for a month. They all share the same shape: users pick a squad, real matches happen, and points get calculated from what those players actually did on the pitch.

This guide shows you how to build a fantasy football app using GOAL API for the data layer — the player pool, confirmed lineups, and the live match events that drive scoring. The framework and database are yours; GOAL API supplies the football.

Get your free API key to follow along: goal-api.com/signup

The Fantasy Football Data Model

Before any code, get the shape clear. Fantasy football is simple in structure — five entities:

Entity What it is Who owns it
Players Every eligible player, with position & team GOAL API (data) + your cache
Squads Each user's picked players within a budget/rules Your database
Lineups The starting XI + captain a user sets per gameweek Your database
Match events Goals, assists, cards — the raw scoring inputs GOAL API
Scores Fantasy points computed from events × your rules Your app's scoring engine

The boundary in a fantasy football app: GOAL API provides the player pool, confirmed lineups and match events; your app owns squads and budgets, the scoring rules and points engine, and leaderboards

GOAL API provides the football reality — who played, who scored, who got booked. Your app owns the fantasy layer — squads, rules, budgets, and the points engine. Keep that boundary clean and the build is straightforward.

Step 1: Get the Player Pool

Your users pick from a pool of players. Build that pool from the leagues your game covers — which leagues are available is on the coverage page. Fetch players and cache them with position and team; the pool is largely stable across a season, so this is a build-time or daily job, not a per-request call.

JavaScript
// Search players and cache the pool (Node example)
async function buildPlayerPool(query) {
  const res = await fetch(
    `https://api.goal-api.com/v1/players/search?q=${encodeURIComponent(query)}`,
    { headers: { Authorization: `Bearer ${process.env.GOAL_API_KEY}` } }
  );
  const { data } = await res.json();
  return data.map(p => ({
    id: p.id,
    apiId: p.apiId,      // the key match events use to name a player
    name: p.name,
    position: p.type,    // 'Goalkeepers' | 'Defenders' | 'Midfielders' | 'Forwards'
    team: p.team?.name,
  }));
}

Endpoint: GET /players/search?q= — and GET /players/:id/statistics for each player's season numbers, useful for showing form during squad selection.

Step 2: Squad Selection & Validation

This step is entirely your app's logic — it's the fantasy layer. Users pick a squad (commonly 15 players) within a budget and position constraints. Store the squad in your own database keyed to the user. GOAL API isn't involved here beyond having supplied the player pool; validation, budgets, and rules are yours to define.

JavaScript
// Pseudocode — your rules, your database
function validateSquad(squad, budget) {
  const cost = squad.reduce((sum, p) => sum + p.price, 0);
  const gk   = squad.filter(p => p.position === 'Goalkeepers').length;
  return cost <= budget && gk === 2 && squad.length === 15;
}
// Persist to YOUR db:  squads[userId] = squad;

Step 3: Confirmed Lineups Before Kickoff

Fantasy games live and die on lineups. A user's captain being benched changes everything. About an hour before kickoff, confirmed lineups become available — pull them so your app knows who actually starts.

JavaScript
async function getConfirmedLineup(fixtureId) {
  const res = await fetch(
    `https://api.goal-api.com/v1/fixtures/${fixtureId}/lineups`,
    { headers: { Authorization: `Bearer ${process.env.GOAL_API_KEY}` } }
  );
  const { data } = await res.json();
  // { home: { startingLineups, substitutes, coach }, away: {...},
  //   homeFormation, awayFormation }. Each player has playerKey (= apiId).
  return data;
}

Endpoint: GET /fixtures/:id/lineups

Use this to flag users whose captain or key players didn't make the XI, and to decide which of a user's picks are eligible to score this gameweek.

Step 4: Live Scoring From Match Events

This is the heart of it. Fantasy points come from real match events — goals, assists, cards. Read each fixture's goals and cards and translate them into your scoring rules.

JavaScript
const api = async (path) => (await (await fetch(`https://api.goal-api.com/v1${path}`, {
  headers: { Authorization: `Bearer ${process.env.GOAL_API_KEY}` },
})).json()).data;

// Goals (with assists) and cards are two endpoints.
async function getMatchEvents(fixtureId) {
  const [goals, cards] = await Promise.all([
    api(`/fixtures/${fixtureId}/events`),
    api(`/fixtures/${fixtureId}/cards`),
  ]);
  return { goals, cards };
}

Endpoints: GET /fixtures/:id/events (goals and assists) | GET /fixtures/:id/cards

A simple points engine

JavaScript
// Map real events -> fantasy points (your rules)
const POINTS = { goal: 5, assist: 3, yellow: -1, red: -3 };

// Events name players by apiId (the lineup's playerKey), not by id.
function scoreEvents({ goals, cards }, apiId) {
  let pts = 0;
  for (const g of goals) {
    const scorer = g.homeScorerId || g.awayScorerId;
    const name   = g.homeScorer || g.awayScorer || '';
    // An own goal is listed under the player who put it in; don't reward it.
    if (scorer === apiId && !name.includes('(o.g.)')) pts += POINTS.goal;
    if ((g.homeAssistId || g.awayAssistId) === apiId) pts += POINTS.assist;
  }
  for (const c of cards) {
    if ((c.homePlayerId || c.awayPlayerId) !== apiId) continue;
    if (c.card === 'yellow card') pts += POINTS.yellow;
    if (c.card === 'red card')    pts += POINTS.red;
  }
  return pts;
}

The points values are yours to set — this is what makes your game distinct. GOAL API gives you the events; the rules are your product.

Step 5: Real-Time Points With WebSocket

Polling every fixture's events every 30 seconds works, but during a busy gameweek it means a lot of requests and a lag between a goal and the points updating. The WebSocket connection pushes events the instant they happen, so a user watching the match sees their points move in real time.

JavaScript
// A short-lived token keeps your API key out of the URL
const { data: { token } } = await (await fetch('https://api.goal-api.com/v1/ws/token', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.GOAL_API_KEY}` },
})).json();

// Subscribe to a match; update points on each event
const ws = new WebSocket(`wss://api.goal-api.com/ws?wsToken=${token}`);

// The first message must authenticate, with the same token
ws.addEventListener('open', () => ws.send(JSON.stringify({ type: 'auth', token })));

ws.addEventListener('message', (e) => {
  const msg = JSON.parse(e.data);
  if (msg.type === 'auth_success') {
    ws.send(JSON.stringify({ type: 'subscribe', resource: 'match', matchId: fixtureId }));
  }
  if (msg.type === 'match_update') {
    // msg.data.id is the fixture; msg.data.goalscorer / cards carry the new events.
    // Recompute affected users' points from the new event
    applyLiveEvent(msg.data);
  }
});

The full connection, auth, and reconnection pattern is in the live scores over WebSocket guide. If you'd rather not hold a persistent connection, webhook events can POST goal.scored to your server instead — the trade-offs are covered in the polling vs WebSocket vs webhooks post.

Step 6: Leaderboards

Leaderboards are pure application logic on top of the points you've already computed. Sum each user's gameweek points in your database and rank them. No API call needed — this is your data now.

SQL
-- Your database, your query
SELECT user_id, SUM(gameweek_points) AS total
FROM fantasy_scores
GROUP BY user_id
ORDER BY total DESC;

Endpoints You'll Use

Endpoint Method Role in your fantasy app
/players/search?q= GET Build the player pool
/players/:id/statistics GET Show player form during selection
/fixtures/date/:date GET Find the gameweek's fixtures
/fixtures/:id/lineups GET Confirmed starting XI before kickoff
/fixtures/:id/events GET Raw scoring inputs: goals and assists
/fixtures/:id/cards GET Raw scoring inputs: cards
/leagues/:id/top-scorers GET Optional: highlight in-form players
wss://api.goal-api.com/ws WS Real-time points updates

What to Build Next

Captain multipliers & chips — layer game mechanics on top of the points engine. Pure app logic; the event data doesn't change.

Player form widgets — use /players/:id/statistics and /leagues/:id/top-scorers to help users pick.

Live mini-league view — combine the WebSocket feed with your leaderboard query so a private league updates live during matches.

Deadline automation — lock squads at the first kickoff of the gameweek using /fixtures/date/:date kickoff times.

Start building free → goal-api.com/signup | No credit card | Every endpoint except odds and predictions

Keep reading

Build a Fantasy Football App: Live Lineups, Player Stats & Real-Time Scoring | GOAL API