← Todas las guías

Crea una tabla de clasificación del campeonato en vivo

Muestra el campeonato de pilotos de MotoGP, Moto2 o Moto3 en tu web mediante un pequeño proxy con caché que mantiene tu clave de RapidAPI fuera del navegador.

10 min de lectura

Una tabla de clasificación es el widget de MotoGP más habitual en sitios de noticias, blogs de aficionados y páginas de clubes. Esta guía construye una en dos partes:

  1. Un proxy — un diminuto endpoint de servidor que guarda tu clave y cachea las respuestas.
  2. Un widget — HTML y JavaScript simples que pintan la tabla a partir del proxy.

1. El proxy

El navegador nunca debe ver tu clave de RapidAPI, así que la página habla con tu propio endpoint. Este ejemplo usa Node.js 18+ y Express; las mismas 30 líneas funcionan como función de Netlify, Vercel o Cloudflare.

// server.js — npm install express
import express from "express";

const HOST = "motogp-api-championship-data-statistics.p.rapidapi.com";
const KEY = process.env.RAPIDAPI_KEY;
if (!KEY) throw new Error("RAPIDAPI_KEY is not set");

const CATEGORIES = new Set(["MOTOGP", "MOTO2", "MOTO3"]);
const TTL_MS = 60 * 60 * 1000; // 1 hour
const cache = new Map();

async function cached(path) {
  const hit = cache.get(path);
  if (hit && Date.now() - hit.at < TTL_MS) return hit.body;
  const res = await fetch(`https://${HOST}${path}`, {
    headers: { "X-RapidAPI-Key": KEY, "X-RapidAPI-Host": HOST },
  });
  if (!res.ok) throw new Error(`Upstream ${res.status}`);
  const body = await res.json();
  cache.set(path, { at: Date.now(), body });
  return body;
}

const app = express();

app.get("/api/standings", async (req, res) => {
  const category = String(req.query.category ?? "MOTOGP").toUpperCase();
  if (!CATEGORIES.has(category)) return res.status(400).json({ error: "Unknown category" });
  try {
    const { data: season } = await cached("/seasons/latest");
    const { data } = await cached(`/standings/seasons/${season.year}/riders?category=${category}`);
    res.set("Cache-Control", "public, max-age=300").json({ season: season.year, category, rows: data });
  } catch (err) {
    res.status(502).json({ error: "Standings are unavailable right now" });
  }
});

app.use(express.static("public"));
app.listen(3000, () => console.log("http://localhost:3000"));

Dos detalles importan:

  • Valida la entrada. Solo se reenvían las tres categorías conocidas, para que nadie pueda usar tu proxy para gastar tu cuota en endpoints arbitrarios.
  • Cachea. Una llamada upstream por hora y categoría atiende a todos los visitantes.

2. El widget

<!-- public/index.html -->
<div class="standings">
  <div role="tablist">
    <button data-category="MOTOGP" aria-selected="true">MotoGP</button>
    <button data-category="MOTO2">Moto2</button>
    <button data-category="MOTO3">Moto3</button>
  </div>
  <table>
    <caption></caption>
    <thead>
      <tr><th>Pos</th><th>Rider</th><th>Team</th><th>Wins</th><th>Pts</th></tr>
    </thead>
    <tbody></tbody>
  </table>
</div>

<script type="module">
  const tbody = document.querySelector(".standings tbody");
  const caption = document.querySelector(".standings caption");

  function cell(text) {
    const td = document.createElement("td");
    td.textContent = text; // textContent, never innerHTML, for data from an API
    return td;
  }

  async function load(category) {
    caption.textContent = "Loading…";
    const res = await fetch(`/api/standings?category=${category}`);
    if (!res.ok) {
      caption.textContent = "Standings are unavailable right now.";
      tbody.replaceChildren();
      return;
    }
    const { season, rows } = await res.json();
    caption.textContent = `${season} ${category} riders' championship`;
    tbody.replaceChildren(
      ...rows.map((r) => {
        const tr = document.createElement("tr");
        tr.append(
          cell(r.position),
          cell(`#${r.rider_number} ${r.rider_name}`),
          cell(r.team_name),
          cell(r.wins),
          cell(r.total_points),
        );
        return tr;
      }),
    );
  }

  document.querySelectorAll("[data-category]").forEach((button) => {
    button.addEventListener("click", () => {
      document.querySelectorAll("[data-category]").forEach((b) => b.setAttribute("aria-selected", b === button));
      load(button.dataset.category);
    });
  });

  load("MOTOGP");
</script>

Ejecuta RAPIDAPI_KEY=... node server.js y abre http://localhost:3000.

Variaciones

  • Diferencia con el líder. Añade una columna con rows[0].total_points - r.total_points.
  • Campeonato de equipos. Apunta una segunda ruta a /standings/seasons/{year}/teams — las filas traen team_name y total_points.
  • Constructores. /standings/seasons/{year}/manufacturers devuelve filas con manufacturer_name.
  • Sitios estáticos. Obtén la clasificación en tu paso de build (Astro, Next.js, Hugo) y reconstruye tras cada fin de semana de carrera — sin necesidad de proxy.
Siguiente guía → Páginas de perfil de piloto con estadísticas de carrera