Crie uma tabela de classificação do campeonato ao vivo
Exiba o campeonato de pilotos de MotoGP, Moto2 ou Moto3 no seu site por meio de um pequeno proxy com cache que mantém sua chave RapidAPI fora do navegador.
10 min de leitura
Uma tabela de classificação é o widget de MotoGP mais comum em sites de notícias, blogs de fãs e páginas de clubes. Este guia cria uma em duas partes:
- Um proxy — um pequeno endpoint de servidor que guarda sua chave e faz cache das respostas.
- Um widget — HTML e JavaScript simples que renderiza a tabela a partir do proxy.
1. O proxy
O navegador nunca deve ver sua chave RapidAPI, então a página fala com o seu próprio endpoint. Este exemplo usa Node.js 18+ e Express; as mesmas 30 linhas funcionam como função Netlify, Vercel ou 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"));
Dois detalhes importam:
- Valide a entrada. Apenas as três categorias conhecidas são encaminhadas, assim ninguém pode usar seu proxy para gastar sua cota em endpoints arbitrários.
- Use cache. Uma chamada upstream por hora por categoria atende todos os visitantes.
2. O 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>
Execute RAPIDAPI_KEY=... node server.js e abra http://localhost:3000.
Variações
- Diferença para o líder. Adicione uma coluna com
rows[0].total_points - r.total_points. - Campeonato de equipes. Aponte uma segunda rota para
/standings/seasons/{year}/teams— as linhas trazemteam_nameetotal_points. - Construtores.
/standings/seasons/{year}/manufacturersretorna linhas commanufacturer_name. - Sites estáticos. Busque a classificação na etapa de build (Astro, Next.js, Hugo) e refaça o build após cada fim de semana de corrida — sem necessidade de proxy.