
API trivia dla vibe coderów
Quizowa apka w 20 liniach, w weekend - z pomocą AI, bez backendu.
TL;DR
- Zbuduj działającą aplikację quizową trivia - pytanie, cztery odpowiedzi, natychmiastowy feedback, przycisk do następnego pytania.
- Stack: Vite + React + TypeScript + REST API QuizBase. Zero backendu, zero bazy danych.
- Czas do pierwszego pytania na ekranie: ~10 minut. Wdrożenie: ~2 minuty więcej.
- Darmowy plan - bez karty kredytowej. Skopiuj poniższy prompt do Cursora albo Claude Code, a cała aplikacja napisze się sama.
Po co to istnieje
Chcesz w ten weekend wypuścić aplikację quizową. Może to projekt poboczny, strona lead-gen do twojego SaaS-a, narzędzie z fiszkami, imprezowy bot na Slacka albo aplikacja edukacyjna dla dzieci. Mechanika jest zawsze ta sama - pobierz pytanie, wyrenderuj cztery przyciski, zareaguj na odpowiedź. Najtrudniejsze nie jest UI. Najtrudniejsze są dane.
Dziś masz trzy słabe opcje. Open Trivia DB jest darmowe i lubiane, ale działa tylko po angielsku, kategorii jest ledwie dwadzieścia kilka, a projekt jest praktycznie nieutrzymywany - nowe pytania pojawiają się rzadko i nie ma roadmapy. Pytania generowane przez LLM kuszą, dopóki ich nie wdrożysz: halucynowane odpowiedzi, niespójna trudność, podatność na prompt-injection i rachunek za każde zapytanie rosnący razem z ruchem. Budowa własnego datasetu brzmi szlachetnie, dopóki nie zejdzie ci sto godzin na pozyskiwaniu, deduplikacji, tłumaczeniu i pisaniu dystraktorów, które nie są błędne na pierwszy rzut oka.
QuizBase to trivia API na ten przypadek, gdy chcesz napisać aplikację, a nie kurować dataset. Ponad milion scalonych, zdeduplikowanych pytań - dziś po angielsku i polsku (więcej języków na życzenie), REST plus TypeScript SDK plus serwer MCP, atrybucja i licencja na każdym rekordzie, więc wdrożysz użycie komercyjne bez konsultacji z prawnikiem. Darmowy plan, który naprawdę jest darmowy - bez karty kredytowej, bez triala, bez sztuczki "pierwszy miesiąc gratis, a potem cię obciążymy". Wstępnie rozgrzany cache, p95 poniżej 100 ms.
Co zbudujesz
Działającą aplikację quizową: wybierz kategorię, pobierz pytanie, wyrenderuj treść i cztery odpowiedzi jako przyciski. Kliknij przycisk - natychmiastowy feedback ("Dobrze" albo "Źle, odpowiedź to X"). Kliknij "Następne pytanie" - świeże pytanie, bez przeładowania strony. Przełącznik języka gotowy do dodania w pięciu liniach.
Technologie: jeden komponent (`App.tsx`), bez routera, bez biblioteki do stanu, bez backendu. Klucz API QuizBase siedzi w `.env.local`. Fetch siedzi w `useEffect`. UI to dowolny Tailwind albo czysty CSS, jaki wolisz - w tym tutorialu zostawiamy go nudny i bez stylów, żeby skupić się na przepływie danych.
Na koniec masz statyczną stronę gotową do wdrożenia. `pnpm build` generuje folder `dist/`. Przeciągnij go na Vercel, Netlify albo Cloudflare Pages. Całkowity koszt wdrożenia: zero dolarów, zero serwerów, zero migracji bazy danych.
Stack - z mocnym zdaniem i nudny
Wybieramy **Vite + React + TypeScript**. Nie dlatego, że to najmodniejszy stack, tylko dlatego, że to stack, który ma w pamięci każdy asystent AI na planecie. Cursor, Claude Code, GitHub Copilot - wszystkie generują działający kod dla tej kombinacji, bez prowadzenia za rękę. Jeśli wolisz Next.js, czysty JS albo React Native, warianty są niżej - warstwa danych jest identyczna.
Świadomie pomijamy bibliotekę do stanu. `useState` i `useEffect` wystarczą. Świadomie pomijamy bibliotekę do routingu. Jedna strona wystarczy. Świadomie pomijamy framework UI. Cztery elementy `<button>` z dwiema regułami CSS wyglądają dobrze w weekendowym prototypie, a dodanie Tailwinda to jedno `pnpm dlx tailwindcss init`, gdy zechcesz. Zasada vibe codingu: im mniej zależności, tym mniej halucynacji LLM o tym, jakie API udostępnia dana zależność.
- Vite - natychmiastowy serwer deweloperski, zero konfiguracji, build produkcyjny przez
pnpm build. - React 19 -
useStateiuseEffectto wszystko czego potrzebujesz; bezuseReducer, bez Redux, bez Zustand. - TypeScript - zwraca się przy pierwszej halucynacji LLM o nazwie pola; kompilator ją wyłapuje.
- API QuizBase - endpoint REST, jeden nagłówek do uwierzytelnienia (
X-API-Key), kształt odpowiedzi stabilny od v1. - Bez backendu - klucze publishable (
qb_pk_…) są CORS-safe i mogą siedzieć w bundlu klienckim. Klucze secret (qb_sk_…) działają tylko po stronie serwera.
Zbuduj krok po kroku
Sześć kroków, dziesięć minut. Każdy krok to akapit z wyjaśnieniem, a po nim kod, który możesz wkleić. Żaden kod nie jest ukryty, żadna zależność niewyjaśniona, żaden krok nie mówi "a tu dzieje się magia". Jeśli utkniesz, przeskocz do sekcji z pułapkami poniżej.
Krok 1
Utwórz aplikację Vite + React + TypeScriptPostaw świeży projekt. Szablon `react-ts` daje ci React 19, tryb strict TypeScriptu i gotowy do pracy Vite. Odpalisz tryb dev w mniej naciśnięć klawiszy, niż zajmuje przeczytanie tego akapitu.
pnpm create vite@latest quiz-app -- --template react-ts cd quiz-app pnpm installKrok 2
Zdobądź darmowy klucz API QuizBaseZarejestruj się na quizbase.runriva.com/pricing - wybierz darmowy plan (bez karty, 500 zapytań/dzień, każdy endpoint odblokowany). Po rejestracji w dashboardzie masz przycisk 'Create key'. Wybierz **klucz publishable** (prefiks `qb_pk_…`, etykieta scope w dashboardzie `publishable`) - klucze publishable są CORS-safe i przeznaczone do kodu przeglądarkowego. Skopiuj go od razu (ze względów bezpieczeństwa pokazujemy go jawnie dokładnie jeden raz), a potem wrzuć do nowego pliku `.env.local` w katalogu głównym aplikacji Vite.
.env.local (w Vite domyślnie w gitignore) VITE_QUIZBASE_KEY=qb_pk_your_key_hereKrok 3
Pobierz swoje pierwsze pytanieOtwórz `src/App.tsx` i zastąp jego zawartość pojedynczym `useEffect`, który odpala się raz przy montowaniu. Uderzamy w `/v1/questions` z trzema filtrami - kategoria, język, limit - i kluczem publishable w nagłówku `X-API-Key`. Odpowiedź to JSON; `data[0]` to twoje pytanie, w którym `text`, `correctAnswer` i `incorrectAnswers` są osobnymi polami.
src/App.tsx - minimalny fetch import { useEffect, useState } from 'react'; interface Question { id: string; text: string; correctAnswer: string; incorrectAnswers: string[]; } export default function App() { const [question, setQuestion] = useState<Question | null>(null); useEffect(() => { fetch( 'https://quizbase.runriva.com/api/v1/questions' + '?category=science-and-nature&lang=en&limit=1', { headers: { 'X-API-Key': import.meta.env.VITE_QUIZBASE_KEY } } ) .then((r) => r.json()) .then((d) => setQuestion(d.data[0])); }, []); if (!question) return <p>Loading…</p>; return <h1>{question.text}</h1>; }Krok 4
Wyrenderuj cztery odpowiedziAPI zwraca `correctAnswer` i `incorrectAnswers` osobno, żebyś mógł sam zdecydować, czy je przetasować. W quizie chcemy je wymieszać - inaczej poprawna odpowiedź zawsze jest ostatnia i gra rozwiązuje się sama. `useMemo` zależny od `question.id` tasuje raz na pytanie i pamięta kolejność między re-renderami.
Dodaj wewnątrz komponentu import { useEffect, useMemo, useState } from 'react'; // … const choices = useMemo(() => { if (!question) return []; return [question.correctAnswer, ...question.incorrectAnswers] .map((value) => ({ value, sort: Math.random() })) .sort((a, b) => a.sort - b.sort) .map(({ value }) => value); }, [question?.id]); // JSX return ( <main style={{ maxWidth: 600, margin: '2rem auto', fontFamily: 'system-ui' }}> <h1>{question.text}</h1> <ul style={{ listStyle: 'none', padding: 0 }}> {choices.map((c) => ( <li key={c} style={{ margin: '0.5rem 0' }}> <button style={{ padding: '0.5rem 1rem', width: '100%' }}>{c}</button> </li> ))} </ul> </main> );Krok 5
Obsłuż odpowiedzi - dobrze albo źleŚledź, który wariant kliknął użytkownik. Porównaj z `question.correctAnswer`. Pokaż baner. Wyłącz przyciski po odpowiedzi - nikt nie chce się rozmyślać, gdy już się zdecydował. To najmniejsza porcja stanu, dzięki której aplikacja zaczyna działać jak prawdziwy quiz.
Zastąp return JSX const [selected, setSelected] = useState<string | null>(null); const isCorrect = selected === question.correctAnswer; return ( <main style={{ maxWidth: 600, margin: '2rem auto', fontFamily: 'system-ui' }}> <h1>{question.text}</h1> <ul style={{ listStyle: 'none', padding: 0 }}> {choices.map((c) => ( <li key={c} style={{ margin: '0.5rem 0' }}> <button disabled={selected !== null} onClick={() => setSelected(c)} style={{ padding: '0.5rem 1rem', width: '100%', background: selected === c ? (isCorrect ? '#c8f7c5' : '#f7c5c5') : undefined }} > {c} </button> </li> ))} </ul> {selected && ( <p> {isCorrect ? '✓ Correct!' : `✗ Wrong — the answer was ${question.correctAnswer}.`} </p> )} </main> );Krok 6
Dodaj przycisk Następne i wdróż na produkcjęOwiń fetch w funkcję, wywołaj ją przy montowaniu i przy kliknięciu 'Następne pytanie', wyzeruj stan `selected` gdy przyjdzie nowe pytanie. Potem zbuduj (`pnpm build`) i przeciągnij folder `dist/` na Vercel, Netlify albo Cloudflare Pages. Cała aplikacja jest statyczna - bez serwera, bez środowiska poza wstrzykniętym przy buildzie `VITE_QUIZBASE_KEY`.
Build produkcyjny pnpm build # dist/ is your deployable folder — drag it to vercel.com, netlify.com, or pages.cloudflare.com
Kompletna aplikacja - jeden plik, gotowy do skopiowania
Jeśli pominąłeś walkthrough i chcesz po prostu gotowca, oto cały `src/App.tsx`. Wrzuć go, dodaj `VITE_QUIZBASE_KEY` do `.env.local`, uruchom `pnpm dev`.
import { useEffect, useMemo, useState } from 'react';
interface Question {
id: string;
text: string;
correctAnswer: string;
incorrectAnswers: string[];
}
const API = 'https://quizbase.runriva.com/api/v1/questions';
const KEY = import.meta.env.VITE_QUIZBASE_KEY;
async function fetchQuestion(): Promise<Question> {
const r = await fetch(`${API}?category=science-and-nature&lang=en&limit=1`, {
headers: { 'X-API-Key': KEY }
});
const d = await r.json();
return d.data[0];
}
export default function App() {
const [question, setQuestion] = useState<Question | null>(null);
const [selected, setSelected] = useState<string | null>(null);
useEffect(() => {
void fetchQuestion().then(setQuestion);
}, []);
const choices = useMemo(() => {
if (!question) return [];
return [question.correctAnswer, ...question.incorrectAnswers]
.map((value) => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
}, [question?.id]);
if (!question) return <p>Loading…</p>;
const isCorrect = selected === question.correctAnswer;
return (
<main style={{ maxWidth: 600, margin: '2rem auto', fontFamily: 'system-ui' }}>
<h1>{question.text}</h1>
<ul style={{ listStyle: 'none', padding: 0 }}>
{choices.map((c) => (
<li key={c} style={{ margin: '0.5rem 0' }}>
<button
disabled={selected !== null}
onClick={() => setSelected(c)}
style={{
padding: '0.5rem 1rem',
width: '100%',
background:
selected === c ? (isCorrect ? '#c8f7c5' : '#f7c5c5') : undefined
}}
>
{c}
</button>
</li>
))}
</ul>
{selected && (
<p>
{isCorrect
? '✓ Correct!'
: `✗ Wrong — the answer was ${question.correctAnswer}.`}
</p>
)}
<button
onClick={() => {
setSelected(null);
void fetchQuestion().then(setQuestion);
}}
style={{ marginTop: '1rem', padding: '0.5rem 1rem' }}
>
Next question
</button>
</main>
);
}Pobierz darmowy klucz API na /pricing - bez karty kredytowej.
Niech AI to napisze - Cursor, Claude Code, Copilot
Jesteś vibe coderem. Jasne, że chcesz, żeby AI to napisało za ciebie. Oto trzy prompty, które wklejasz do Cursora, Claude Code albo GitHub Copilota - każdy daje działającą aplikację w mniej niż dwie minuty. Prompty zawierają dokładny kształt odpowiedzi API, a to właśnie decyduje o tym, czy kod zadziała za pierwszym razem, czy zhalucynuje `question.options` zamiast `question.incorrectAnswers`.
Cursor
Cursor to najszybsza droga. Composer (Cmd+I na macOS, Ctrl+I na Windows/Linux) przyjmuje jeden prompt i przepisuje pliki twojego projektu za jednym zamachem. Otwórz świeżą aplikację Vite, wciśnij skrót, wklej poniższy prompt. W mniej niż dwie minuty masz działającą aplikację quizową - bez scaffoldingu, bez zakładki ze Stack Overflow.
Jak użyć: Otwórz katalog główny projektu w Cursorze → Cmd+I (Ctrl+I) → wklej prompt → wciśnij Enter → przejrzyj proponowaną edycję → Accept.
Build a trivia quiz app using the QuizBase API.
Stack: React + TypeScript + Vite. One component in src/App.tsx. No router, no state library, no styling framework — inline styles are fine.
API call:
GET https://quizbase.runriva.com/api/v1/questions?category=science-and-nature&lang=en&limit=1
Header: X-API-Key: <value from import.meta.env.VITE_QUIZBASE_KEY>
Response shape:
{
"data": [{
"id": "uuid",
"text": "What is...?",
"correctAnswer": "Answer",
"incorrectAnswers": ["Wrong 1", "Wrong 2", "Wrong 3"]
}]
}
Requirements:
1. Fetch one question on mount (useEffect).
2. Render the question and four answer choices as buttons (shuffle correct + incorrect with useMemo keyed on question.id).
3. Click a button → highlight it green if correct, red if wrong; show "Correct!" or "Wrong — the answer was X" below.
4. Disable all buttons after one is clicked.
5. Show a "Next question" button that fetches a new question and resets selection.
6. Loading state ("Loading…") before the first question arrives.
Use TypeScript types for the Question shape. No external deps beyond React.Claude Code
Claude Code działa w terminalu i edytuje pliki bezpośrednio. Jeśli mieszkasz w podzielonym terminalu (jedno okno vim/emacs/neovim, drugie z Claude), to opcja dla ciebie. Ten sam prompt, inny sposób wywołania - Claude proponuje edycje plików jedna po drugiej i czeka, aż każdą zaakceptujesz albo odrzucisz.
Jak użyć: Z katalogu głównego projektu: `claude`, a potem wklej prompt. Claude przeczyta istniejący App.tsx, zaproponuje przepisanie i zapyta 'Accept this edit?'. Powiedz tak.
Build a trivia quiz app using the QuizBase API.
Stack: React + TypeScript + Vite. One component in src/App.tsx. No router, no state library, no styling framework — inline styles are fine.
API call:
GET https://quizbase.runriva.com/api/v1/questions?category=science-and-nature&lang=en&limit=1
Header: X-API-Key: <value from import.meta.env.VITE_QUIZBASE_KEY>
Response shape:
{
"data": [{
"id": "uuid",
"text": "What is...?",
"correctAnswer": "Answer",
"incorrectAnswers": ["Wrong 1", "Wrong 2", "Wrong 3"]
}]
}
Requirements:
1. Fetch one question on mount (useEffect).
2. Render the question and four answer choices as buttons (shuffle correct + incorrect with useMemo keyed on question.id).
3. Click a button → highlight it green if correct, red if wrong; show "Correct!" or "Wrong — the answer was X" below.
4. Disable all buttons after one is clicked.
5. Show a "Next question" button that fetches a new question and resets selection.
6. Loading state ("Loading…") before the first question arrives.
Use TypeScript types for the Question shape. No external deps beyond React.GitHub Copilot
Copilot sterujesz komentarzami. Piszesz specyfikację jako blok komentarza na górze pliku, zostawiasz szkielet TypeScriptu i wciskasz Tab wewnątrz pustej funkcji. Copilot dopisuje resztę. Najlepszy, gdy chcesz się uczyć, czytając linijka po linijce to, co pisze LLM - podpowiada po jednej instrukcji naraz, więc widzisz każdą decyzję.
Jak użyć: Wklej blok komentarza + szkielet poniżej do świeżego `src/App.tsx`, ustaw kursor wewnątrz pustej funkcji i wciśnij Tab. Akceptuj każdą podpowiedź przez Tab, odrzucaj przez Esc.
// Trivia quiz app powered by the QuizBase API
// API: GET https://quizbase.runriva.com/api/v1/questions?category=science-and-nature&lang=en&limit=1
// Auth: X-API-Key header from import.meta.env.VITE_QUIZBASE_KEY
// Response shape: { data: [{ id, text, correctAnswer, incorrectAnswers: string[] }] }
//
// Requirements:
// - Fetch question on mount with useEffect
// - Shuffle correctAnswer + incorrectAnswers into four buttons (useMemo keyed on question.id)
// - On click: highlight correct=green / wrong=red, disable buttons, show feedback text
// - "Next question" button refetches and resets selection
// - Loading state before first question
import { useEffect, useMemo, useState } from 'react';
interface Question {
id: string;
text: string;
correctAnswer: string;
incorrectAnswers: string[];
}
export default function App() {
// Press Tab and let Copilot write the rest
}Pomiń kod całkowicie - połącz się przez MCP
Możesz też pominąć REST API i pozwolić swojemu asystentowi AI gadać z QuizBase bezpośrednio przez Model Context Protocol. Serwer MCP pod `https://quizbase.runriva.com/mcp` udostępnia 12 otypowanych narzędzi - twoje AI widzi je jako natywne możliwości, z gotowym schematem i kształtem odpowiedzi z góry. Bez boilerplate'u fetcha, bez halucynacji nazw pól, bez klienckiego SDK, które trzeba utrzymywać w synchronizacji.
- Zero kodu do napisania - twój asystent AI dostaje
quizbase_random,quizbase_list,quizbase_question_by_idi jeszcze 9 narzędzi jako natywne możliwości. - Koniec z "ale LLM zhalucynował nazwy moich pól" - serwer MCP podaje AI dokładny schemat narzędzia, parametry i kształt odpowiedzi z góry.
- Ten sam klucz, ten sam darmowy plan - twój
qb_pk_…działa dla MCP i REST tak samo. Jeden licznik, jedno miejsce do rotacji. - Działa obok REST - podłącz MCP do generowania pomysłów i prototypowania w Cursorze, a właściwą aplikację wypuść na REST, gdy będziesz gotowy na produkcję.
Cursor
Cursor czyta `.cursor/mcp.json` (per-projekt) albo `~/.cursor/mcp.json` (globalnie). Wrzuć snippet poniżej, zrestartuj Cursora, a narzędzia `quizbase_*` pojawią się w composerze. Teraz możesz napisać prompt "daj mi pięć losowych pytań z historii po polsku", a Cursor wywoła narzędzie bezpośrednio - bez fetcha, bez obsługi klucza API, bez zgadywania kształtu.
{
"mcpServers": {
"quizbase": {
"url": "https://quizbase.runriva.com/mcp",
"headers": {
"Authorization": "Bearer qb_pk_your_key_here"
}
}
}
}Wypróbuj: W composerze Cursora: "Używając serwera MCP quizbase, wygeneruj codzienny quiz historyczny do mojej aplikacji React - pięć pytań o trudności medium po polsku, wyrenderuj je jako statyczną stronę." Cursor wywołuje `quizbase_random` z tymi filtrami i pisze stronę React za jednym razem.
Claude Code (CLI)
Claude Code używa subkomendy `claude mcp`. Jedna linia i masz 12 nowych narzędzi, które może wywołać twój asystent AI prosto z terminala.
claude mcp add quizbase \
--transport http \
--url https://quizbase.runriva.com/mcp \
--header "Authorization: Bearer qb_pk_your_key_here"
# verify
claude mcp listWypróbuj: W Claude Code: "Zbuduj grę trivia opartą na quizbase - 10 rund, mieszane kategorie, z timerem." Claude wywołuje najpierw `quizbase_categories`, żeby zobaczyć co jest dostępne, potem `quizbase_random` dziesięć razy z różnymi filtrami i pisze całą aplikację plik po pliku.
Claude.ai / Claude Desktop (Custom Connectors)
Claude.ai w przeglądarce i Claude Desktop wspierają Custom Connectors - serwery MCP po HTTP konfigurowane przez UI, bez pliku JSON. Najlepsze, gdy eksplorujesz pomysły w czacie konwersacyjnym, zanim zdecydujesz się na kod.
Name: QuizBase
URL: https://quizbase.runriva.com/mcp
Auth: Bearer token
Token: qb_pk_your_key_hereWypróbuj: W zwykłym czacie Claude.ai: "Zrób burzę mózgów pięciu pomysłów na aplikacje quizowe używając QuizBase, a potem dla najbardziej obiecującego daj mi starter Vite + React, który mogę wkleić do świeżego projektu." Claude wywołuje `quizbase_categories` i `quizbase_languages`, żeby zrozumieć dataset, a potem projektuje aplikację i produkuje kod.
Chcesz przetestować zanim cokolwiek skonfigurujesz? /playground/mcp to nasz interaktywny tester MCP - wklej klucz, zobacz wszystkie 12 narzędzia, uruchom je z poziomu formularza, skopiuj JSON.
Albo: zapytaj swoje AI co zbudować
Szczerze? Twoje AI prawdopodobnie ma lepsze pomysły niż my. Mamy jednoplikowy pakiet dokumentacji pod `https://quizbase.runriva.com/llms-full.txt` - każdy endpoint, każdy parametr, każde pole odpowiedzi, sformatowane pod konsumpcję przez LLM (jeden fetch, bez scrapowania). Wklej prompt poniżej do Cursora, Claude Code albo nawet ChatGPT. Przeczyta naszą dokumentację, przeprowadzi burzę mózgów, a ty możesz zbudować coś, o czym nigdy nie pomyśleliśmy.
I want to build something with the QuizBase API.
Their full developer documentation as a single document is at:
https://quizbase.runriva.com/llms-full.txt
(That URL is designed to be fetched as plain text — every endpoint, every parameter, every response field. You can read it with one fetch.)
Their MCP server is at https://quizbase.runriva.com/mcp — if you have MCP support, you can call `quizbase_random`, `quizbase_list`, `quizbase_question_by_id`, `quizbase_categories`, `quizbase_topics`, `quizbase_topic_by_slug`, `quizbase_tags`, `quizbase_subcategories`, `quizbase_languages`, `quizbase_stats`, and `quizbase_report` directly.
Read the docs, look at what is possible, and brainstorm with me. Specifically:
1. What are five interesting things I could build that go beyond a basic single-question quiz?
2. For each idea, what API endpoints / MCP tools would I use, and roughly how much code would it take?
3. Which idea is the best fit for a weekend project — small enough to ship but interesting enough that someone would actually use it?
4. Pick that one and let's start building. Ask me anything you need to know.Tak chcemy, żeby to działało. Dokumentacja jest otwarta, dataset jest otwarty, serwer MCP jest otwarty. Nie wiemy, co ludzie zbudują z QuizBase. O to właśnie chodzi. Jeśli wypuścisz coś fajnego, daj nam znać - strona `/about` ma dane kontaktowe. Pokażemy to innym.
Zobacz to w akcji

Inne stacki - to samo API, ten sam pomysł
Vite + React to najłatwiejszy punkt startu, ale nie jedyny. Wywołanie API QuizBase jest identyczne między runtime'ami - zmienia się tylko warstwa renderowania. Oto trzy kolejne warianty, które możesz skopiować.
Next.js (App Router + Server Component)
Kiedy użyć: Chcesz renderowania po stronie serwera, ukrytego klucza API (użyj `qb_sk_…` tylko po stronie serwera), HTML przyjaznego SEO, albo masz już projekt Next.js. Klucz QuizBase nigdy nie trafia do bundla klienckiego.
// app/page.tsx
import { revalidatePath } from 'next/cache';
async function fetchQuestion() {
const r = await fetch(
'https://quizbase.runriva.com/api/v1/questions?category=science-and-nature&lang=en&limit=1',
{
headers: { 'X-API-Key': process.env.QUIZBASE_SECRET_KEY! },
cache: 'no-store' // fresh question every request
}
);
return (await r.json()).data[0];
}
export default async function Page() {
const q = await fetchQuestion();
return (
<main>
<h1>{q.text}</h1>
<ul>
{[q.correctAnswer, ...q.incorrectAnswers].map((c) => (
<li key={c}>{c}</li>
))}
</ul>
<form action={async () => { 'use server'; revalidatePath('/'); }}>
<button type="submit">Next question</button>
</form>
</main>
);
}Vanilla JS (pojedynczy plik HTML, bez kroku build)
Kiedy użyć: Chcesz prototyp w jednym pliku, embed na statyczną stronę, albo nauczyć początkującego API bez tłumaczenia "co to jest npm". Otwórz plik w przeglądarce - działa.
<!doctype html>
<html lang="en">
<body>
<main id="app">Loading…</main>
<script>
const KEY = 'qb_pk_your_key_here'; // publishable key — CORS-safe in browser
async function load() {
const r = await fetch(
'https://quizbase.runriva.com/api/v1/questions?lang=en&limit=1',
{ headers: { 'X-API-Key': KEY } }
);
const q = (await r.json()).data[0];
const choices = [q.correctAnswer, ...q.incorrectAnswers]
.sort(() => Math.random() - 0.5);
document.getElementById('app').innerHTML = `
<h1>${q.text}</h1>
${choices.map((c) => `<button data-c="${c}">${c}</button>`).join('')}
<p id="feedback"></p>
<button id="next">Next</button>
`;
document.querySelectorAll('[data-c]').forEach((b) => {
b.onclick = () => {
const correct = b.dataset.c === q.correctAnswer;
document.getElementById('feedback').textContent = correct
? '✓ Correct!' : '✗ Wrong — answer was ' + q.correctAnswer;
};
});
document.getElementById('next').onclick = load;
}
load();
</script>
</body>
</html>React Native (Expo)
Kiedy użyć: Wypuszczasz mobilną grę quizową. Expo pozwala wypuścić na iOS, Androida i web z jednego codebase. To samo wywołanie API QuizBase, ta sama obsługa klucza - po prostu `View` i `Text` zamiast `div` i `p`.
import { useEffect, useState } from 'react';
import { Button, FlatList, Text, View } from 'react-native';
export default function App() {
const [q, setQ] = useState<any>(null);
useEffect(() => {
fetch(
'https://quizbase.runriva.com/api/v1/questions?category=science-and-nature&lang=en&limit=1',
{ headers: { 'X-API-Key': process.env.EXPO_PUBLIC_QUIZBASE_KEY! } }
)
.then((r) => r.json())
.then((d) => setQ(d.data[0]));
}, []);
if (!q) return <Text>Loading…</Text>;
const choices = [q.correctAnswer, ...q.incorrectAnswers]
.sort(() => Math.random() - 0.5);
return (
<View style={{ padding: 24, marginTop: 48 }}>
<Text style={{ fontSize: 20, marginBottom: 16 }}>{q.text}</Text>
<FlatList
data={choices}
keyExtractor={(c) => c}
renderItem={({ item }) => (
<Button
title={item}
onPress={() =>
alert(item === q.correctAnswer ? 'Correct!' : 'Wrong')
}
/>
)}
/>
</View>
);
}Pułapki - rzeczy, które cię podejdą
Sześć haczyków, które widzimy w realnych zgłoszeniach do supportu, z faktycznym fixem. Jeśli na któryś trafisz, ta sekcja oszczędza ci piętnaście minut googlowania.
Błędy CORS, gdy wdrażam na produkcję.
Prawdopodobnie używasz klucza secret (
qb_sk_…) w przeglądarce - te celowo nie są CORS-safe. Klucze publishable (qb_pk_…) działają z dowolnego originu i są zaprojektowane do kodu po stronie klienta. Podmień klucz w zmiennej środowiskowej, wdróż ponownie."No questions matched" - pusta tablica `data`.
Twoja kombinacja filtrów jest za wąska. Częsty winowajca: mała kategoria połączona z małym językiem. Usuń jeden filtr (spróbuj samego
?lang=en), żeby zweryfikować, potem dodawaj filtry z powrotem po jednym. Endpoint/v1/questions/randomnigdy nie zwraca pustki dla poprawnych filtrów.Trafiony rate limit (HTTP 429) podczas developmentu.
Darmowy plan to 500 zapytań/dzień na konto. Jeśli robisz hot-reload przy każdym naciśnięciu klawisza,
useEffectpotrafi odpalić się pięćdziesiąt razy na minutę. Trzymaj pytanie w stanie (już to robisz) i użyj?limit=10plus wskaźnika po stronie klienta zamiast jednego fetcha na pytanie. Odpowiedź 429 zawiera nagłówekRetry-After- uszanuj go.Przypadkowo wrzuciłem klucz API do gita.
Natychmiast go zrotuj w dashboardzie (stary klucz zostaje unieważniony, a nowy wydany). Potem:
.env*powinien być w.gitignore(Vite dodaje to automatycznie; sprawdź dla pewności). W projektach zespołowych commituj.env.examplez wartościami zastępczymi zamiast prawdziwych.TypeScript narzeka, że `import.meta.env.VITE_QUIZBASE_KEY` jest `string | undefined`.
Bo jest - Vite nie może udowodnić, że zmienna środowiskowa istnieje w czasie buildu. Albo zrób non-null assert (
!) jeśli masz sprawdzenie w czasie buildu, albo dodajsrc/vite-env.d.tszinterface ImportMetaEnv { readonly VITE_QUIZBASE_KEY: string }. Oficjalne TypeScript SDK (@quizbase/client, npm) ma otypowany kształt odpowiedzi, więc nie musisz definiowaćQuestionręcznie.Mój prompt do Cursora / Claude Code wyprodukował kod, który się nie kompiluje.
Zwykle AI wymyśliło nazwę pola (np.
question.choicesalboquestion.optionszamiast prawdziwychquestion.correctAnswer+question.incorrectAnswers, albodata.questionzamiastdata[0]). Wklej kształt odpowiedzi z bloku promptu powyżej do tego samego czatu i poproś o naprawę. Albo wklej komunikat błędu - zarówno Cursor, jak i Claude Code są dobre w samokorekcie, gdy dasz im wyjście kompilatora.
Najczęściej zadawane pytania
- Czy naprawdę nie potrzebuję żadnego backendu?
- Tak, dla developmentu i projektów na weekend - klucze publishable (`qb_pk_…`, etykieta scope w dashboardzie `publishable`) są CORS-safe z założenia i przeznaczone do życia w kodzie po stronie klienta. Na produkcji masz wybór: dalej używać kluczy publishable po stronie klienta (proste, limitowane per konto, każdy może je odczytać z DevTools, ale nie może zrobić więcej niż pozwala twój plan) albo przejść na klucze secret (`qb_sk_…`) za małym serverless proxy, jeśli chcesz ukryć swoje wzorce użycia przed ciekawskimi. Obie ścieżki są w pełni wspierane.
- Czy darmowy plan jest naprawdę darmowy, bez karty kredytowej?
- Tak. 500 zapytań/dzień, na zawsze, bez podpiętej karty, bez odliczania triala. Darmowy plan istnieje, bo chcemy, żebyś wypróbował API end-to-end zanim zapłacisz. Gdy wdrożysz na produkcję i będziesz potrzebował wyższych limitów, płatne plany zaczynają się od ceny skrojonej pod solo deweloperów - dokładne liczby na stronie cennika.
- Czy mogę to wdrożyć na Vercel, Netlify albo Cloudflare Pages?
- Tak. Uruchom `pnpm build` i przeciągnij powstały folder `dist/` na którykolwiek z trzech. Klucz QuizBase jest wstrzykiwany w czasie buildu (`VITE_QUIZBASE_KEY`), więc ustawiasz go w dashboardzie hostingu jako zmienną środowiskową build-time - reszta to statyczny HTML, CSS i JavaScript.
- Ile języków jest wspieranych?
- Dziś angielski i polski, więcej języków na życzenie. Listę aktywnych wylistujesz przez `GET /v1/languages` - odpowiedź to tablica JSON kodów języków plus liczba pytań gotowych do quizu na każdy kod. Żeby przełączyć język aplikacji, zmień parametr `?lang=` (`en`, `pl`).
- Jak dokładne są pytania?
- Każde pytanie niesie pełny obiekt `attribution` - `source` (z którego źródłowego datasetu: OpenTDB, OpenTriviaQA, MKQA), `author`, `license` i `modifications`. Kurujemy, deduplikujemy i tłumaczymy; nie generujemy pytań przez LLM. Jeśli wypatrzysz błędną odpowiedź, każde pytanie ma endpoint /report i link `Report` w dashboardzie.
- Czemu nie użyć po prostu ChatGPT albo Claude do generowania pytań?
- Trzy problemy. (1) LLM halucynują - odpowiedzi, które wyglądają poprawnie, ale są błędne, zwłaszcza przy datach, biologii i niszowych ciekawostkach. (2) Trudność jest niespójna - ten sam prompt produkuje dziko różne wyzwania między uruchomieniami. (3) Brak łańcucha atrybucji - nie powiesz użytkownikom, skąd wziął się dany fakt. Pytania QuizBase da się prześledzić aż do realnego źródłowego datasetu ze znaną licencją.
- Czy mogę filtrować po tagach, podkategoriach albo topikach?
- Tak. Poza 24 kategoriami najwyższego poziomu każde pytanie ma tagi (nazwy własne: `python`, `napoleon`, `dna`) i podkategorie (szersze grupowania: `european-capitals`, `cellular-biology`). Filtruj przez `?tags=python`, `?subcategory=european-capitals` albo `?topics_any=programming,linguistics`, żeby dostać pytania z któregokolwiek z wielu topików.
- Czy mój agent AI (Claude Desktop, Cursor, ChatGPT) może użyć QuizBase?
- Tak - a jako vibe coder możesz chcieć pominąć REST API całkowicie i użyć MCP zamiast niego. Dostarczamy serwer Model Context Protocol pod `https://quizbase.runriva.com/mcp` (Streamable HTTP). Dodaj go do Cursora, Claude Code albo Claude Desktop, a twoje AI dostaje 12 narzędzi trivia (`quizbase_random`, `quizbase_list`, `quizbase_question_by_id`, `quizbase_topics` i 8 więcej) jako natywne możliwości - bez fetcha, bez zgadywania kształtu API, bez klienckiego SDK. Zobacz sekcję MCP powyżej, żeby skonfigurować jednym snippetem per klient. Jest też `/.well-known/mcp` do auto-discovery i `/openapi.json` do akcji Custom GPT w ChatGPT.
- Czy użycie komercyjne jest dozwolone?
- Tak. API jest licencjonowane komercyjnie zgodnie z twoim planem subskrypcji - to właśnie pokrywa twój plan z /pricing (rate limity, świeżość, support, MCP, SDK, kurowane topiki). Bazowe rekordy źródłowe niosą swoje oryginalne otwarte licencje (większość to CC-BY-SA-4.0, część CC-BY-4.0 / MIT); dump BY-SA pod /data to publikacja share-alike, do której udostępnienia zobowiązuje nas licencja źródłowa. Każda odpowiedź API zawiera obiekt `attribution` na każdy rekord (licencja, autor, źródło, modyfikacje), żebyś mógł spełnić wymogi atrybucji źródłowej bez czytania ani jednej strony tekstu prawnego. Uwaga: share-alike BY-SA stosuje się, jeśli redystrybuujesz surowe rekordy pytań - twoje własne UI, logika punktacji i produkt zbudowany na API są twoje.
- Gdzie dostanę pomoc, jeśli coś się zepsuje?
- Dokumentacja na quizbase.runriva.com/docs, w tym quickstart, pełna referencja API, przewodniki SDK i rozwiązywanie problemów. Przy bugach API albo platformy użyj przycisku "Report a problem" w dashboardzie - automatycznie dołącza twój request_id, endpoint i breadcrumbs Sentry, więc triage jest szybki. Ten sam przycisk jest na każdej stronie błędu. Niezalogowany? Link "Report a bug" w stopce strony otwiera szkic maila. Bugi specyficzne dla SDK trafiają do github.com/maciejdzierzek/quizbase-sdk-ts. Bugi treściowe w pytaniu (błędna odpowiedź, złe tłumaczenie) zgłaszaj przez POST /api/v1/report albo narzędzie MCP quizbase_report. Strona statusu pod /status, jeśli podejrzewasz awarię. Średni czas odpowiedzi to poniżej 48 godzin w dni robocze.
Dziesięć rzeczy do zbudowania dalej - wybierz jedną, wypuść ją w ten weekend
Masz podstawowy quiz. Oto dziesięć kierunków, w które możesz go rozwinąć - każdy na tyle mały, żeby wypuścić go w weekend, każdy na tyle inny, że powstała aplikacja jest osobnym bytem. Połowę z nich twoje AI naszkicuje ci w piętnaście minut, jeśli wkleisz dany pomysł do Cursora i poprosisz.
1. Tryb codziennego wyzwania
Pokaż każdemu odwiedzającemu te same pięć pytań dziś, inne pięć jutro. Użyj daty jako seeda - ta sama data, te same pytania dla wszystkich, idealne pod motyw 'rywalizuj ze znajomymi'. Zmiana w dwóch liniach: wyprowadź deterministyczny seed z daty i przekaż go do `?seed=` (albo użyj `quizbase_random` ze stałym kursorem).
2. Tryb presji czasu Artykuł →
Dodaj pięciosekundowy timer na pytanie. Auto-submit po upływie czasu. Punktuj streak - trzy błędy z rzędu resetują przebieg. Mechanika, która sprawia, że aplikacje trivia naprawdę wciągają, zamiast być "zagram raz i zapomnę".
3. Językowa rękawica
Poproś użytkownika o wybranie trzech języków. Runda pierwsza po angielsku, druga w pierwszym wyborze, trzecia w drugim, czwarta w trzecim. Ta sama trudność pytań między językami - dowodzi, że twoja aplikacja obsługuje prawdziwe i18n, a nie tylko "wspieramy lokalizacje".
4. Głębokie zanurzenie w topik
Pozwól użytkownikowi wpisać topik (np. "biology", "napoleon", "python"). Użyj `?tags=` albo `?topics_any=`, żeby przefiltrować pulę pytań. Teraz to nie "ogólna trivia" - to "biologiczna trivia dla studentów medycyny" albo "trivia o Napoleonie dla zaprzyjaźnionego historycznego nerda".
5. Bot na Slacka / Discorda Artykuł →
Bot z dwoma pytaniami dziennie na kanał społeczności. Wrzuć pytanie o 10:00, odsłoń odpowiedź o 18:00, śledź kto odpowiedział pierwszy. Dwadzieścia linii Node + SDK Slacka/Discorda + API QuizBase.
6. Widget lead-gen z quizem Artykuł →
Osadź pięciopytaniowy quiz na landingu swojego SaaS-a. Po odpowiedzi poproś o e-mail, żeby "zobaczyć jak wypadasz na tle innych". W praktyce: 5-10× wyższa konwersja niż CTA "zapisz się do naszego newslettera". Zbuduj to jako snippet `<script>`, który inni mogą wrzucić na własną stronę.
7. Generator fiszek Anki Artykuł →
Pobierz 50 pytań z kategorii, wyeksportuj jako Anki .apkg (albo CSV dla Quizlet). Jeden przycisk, jeden download.
8. Multiplayer w czasie rzeczywistym Artykuł →
Dwóch graczy, to samo pytanie pokazane obu, pierwszy klika poprawnie i wygrywa rundę. Użyj malutkiego serwera WebSocket (albo darmowego planu Pusher / Ably / Liveblocks) do synchronizacji. Złożoność tkwi w warstwie realtime, nie w trivii - tę część już masz.
9. Codzienny digest e-mailowy
Pięć pytań trivia w mailu, każdego ranka, na topik wybrany przez subskrybenta. Darmowy plan Resend albo Postmark obsługuje dostarczanie. Czas czytania: 90 sekund. Metryka retencji, którą ludzie faktycznie otwierają.
10. Quiz głosowy (Alexa / mobile)
Owiń API w skill głosowy. "Hej Alexa, zapytaj QuizBase o pytanie z nauki." Odpowiedź przez syntezę mowy. Do zrobienia w jedno popołudnie, jeśli kiedykolwiek wypuściłeś skill Alexa - treść trivia to ta łatwa część.
11. Rozszerzenie do Cursora: kod + pomodoro z trivią Artykuł →
Wypuść rozszerzenie do bocznego panelu Cursora (albo VS Code), które wymusza przerwę pomodoro świeżym pytaniem trivia. 25 minut skupienia, potem jedno pytanie QuizBase na panelu z odpowiedzią. Śledzenie streaka utrwalone w stanie rozszerzenia - przetrwa restarty edytora. Około 150 linii TypeScriptu.
Gotowy na wdrożenie?
Darmowy plan, bez karty kredytowej. Indie (9 USD/mc) lub Pro (29 USD/mc), gdy trafiasz do realnych użytkowników.
Co zbudować dalej
Masz działającą aplikację quizową. Oto dokąd stąd pójść - każdy link to konkretny następny krok, nie mgliste "przejrzyj naszą dokumentację".
- Dodaj wielorundową grę ze śledzeniem wyniku →
Zamień demo z jednym pytaniem w 10-rundową grę z bieżącym wynikiem. Przewodnik krok po kroku.
- Przełączaj język pytania w czasie działania →
Dodaj rozwijaną listę języków - angielski i polski są aktywne, a `?lang=` jest gotowy, by udostępnić więcej z datasetu w zależności od popytu. Jeden dodatkowy fetch, jeden dodatkowy stan.
- Podłącz agenta Claude albo Cursor przez MCP →
Pomiń warstwę REST całkowicie. Twój asystent AI dostaje `quizbase_random` i `quizbase_list` jako natywne narzędzia.
- Przejrzyj pełną referencję API →
Każdy filtr, każdy parametr, każde pole odpowiedzi - z przykładami.
- Zainstaluj TypeScript SDK dla pierwszorzędnych typów →
Pomiń ręczny `interface Question`. Autouzupełnianie na każdym polu, każdej odpowiedzi, każdym kodzie błędu.