← 모든 가이드

실시간 챔피언십 순위표 만들기

RapidAPI 키를 브라우저에 노출하지 않는 작은 캐싱 프록시를 통해 MotoGP, Moto2, Moto3 라이더 챔피언십을 웹사이트에 표시합니다.

10분 분량

순위표는 뉴스 사이트, 팬 블로그, 클럽 페이지에서 가장 흔한 MotoGP 위젯입니다. 이 가이드는 두 부분으로 나누어 순위표를 만듭니다.

  1. 프록시 — 키를 보관하고 응답을 캐싱하는 작은 서버 엔드포인트.
  2. 위젯 — 프록시에서 받은 데이터로 표를 렌더링하는 순수 HTML과 JavaScript.

1. 프록시

브라우저는 RapidAPI 키를 절대 보면 안 되므로, 페이지는 자체 엔드포인트와 통신합니다. 이 예제는 Node.js 18+와 Express를 사용하며, 같은 30줄 정도의 코드를 Netlify, Vercel, 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"));

중요한 점이 두 가지 있습니다.

  • 입력을 검증하세요. 알려진 세 카테고리만 전달하므로, 다른 사람이 프록시를 이용해 임의의 엔드포인트에 할당량을 소모할 수 없습니다.
  • 캐싱하세요. 카테고리당 시간당 한 번의 업스트림 호출로 모든 방문자를 처리할 수 있습니다.

2. 위젯

<!-- 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>

RAPIDAPI_KEY=... node server.js를 실행하고 http://localhost:3000을 여세요.

응용

  • 선두와의 격차. rows[0].total_points - r.total_points를 표시하는 열을 추가하세요.
  • 팀 챔피언십. 두 번째 경로를 /standings/seasons/{year}/teams로 연결하세요. 각 행에는 team_name과 total_points가 있습니다.
  • 제조사. /standings/seasons/{year}/manufacturers는 manufacturer_name이 있는 행을 반환합니다.
  • 정적 사이트. 빌드 단계에서 순위를 가져오고(Astro, Next.js, Hugo) 레이스 주말마다 다시 빌드하세요. 프록시가 전혀 필요하지 않습니다.
다음 가이드 → 커리어 통계가 포함된 라이더 프로필 페이지