over-engineered quiz ^^
A small "how well do you know Kasper?" quiz. You enter a name, answer the questions in random order, review your answers, submit, and see your score next to what the correct answers were. Every submission lands on a shared leaderboard.
- Start.
/quiz/startasks for a name (2–30 characters). The quiz routes won't load without one. - Questions. The server shuffles the question order and each question's options, and keeps the order in an
httpOnlycookie (quiz-order, valid for one hour). Progress is kept insessionStoragethrough a persisted Zustand store, so a reload doesn't lose your answers. - Review.
/quiz/reviewlists the questions you haven't answered yet before you submit. - Submit. The server checks the answers with Zod: question ids must exist, answers must have the right shape, values must be real options, and order questions must use every item exactly once. It then scores them and saves the row to Postgres. If the name is already taken, it gets a suffix like
Kasper (2). - Result.
/quiz/resultshows each answer next to the correct one and links to the leaderboard, which jumps to your page and highlights your entry.
The correct answers never leave the server. The client only gets the public version of each question (prompt and options).
Scoring lives in src/lib/scoring.ts, and the total is rounded once before it's saved.
| Type | What you do | Points |
|---|---|---|
select |
Pick one option | 1 if correct |
multi |
Pick several (optional cap with maxOptions) |
Exact match gets one point per correct option. Otherwise +0.5 per right pick and −0.5 per wrong pick, never below 0 |
order |
Drag items into order | Up to 4, in proportion to how many pairs of items are in the right relative order |
The leaderboard ranks by score, and ties go to whoever submitted first. It shows 15 entries per page.
The real questions live in src/config/questions.data.ts. That file is gitignored so the answers stay out of the repo. Copy the example to get started:
cp src/config/questions.data.example.ts src/config/questions.data.tsFor Docker builds, pass the file's contents as the QUESTIONS_DATA_TS build arg and the Dockerfile writes it into place before building.
- Framework: TanStack Start (React 19, SSR) with TanStack Router for file-based routes, and server functions (
createServerFn) in place of a separate API - Build: Vite 8, Nitro for the server output, React Compiler through Babel
- Data: Postgres on Neon (serverless HTTP driver) with Drizzle ORM and drizzle-kit
- State: Zustand (quiz progress, sound, dialogs, drag), TanStack Query
- Validation: Zod 4
- UI: Tailwind CSS 4, shadcn/ui on Base UI, Phosphor icons, Sonner toasts, next-themes for light/dark mode, Geist and Inter fonts
- Interaction: dnd-kit for order questions, TanStack Hotkeys for keyboard navigation, and Web Audio sound effects (Kenney, CC0) inlined as base64
- Tooling: Biome (lint and format), Vitest with Testing Library, TypeScript 6, pnpm
src/
routes/ pages: home, leaderboard, quiz/{start,$step,review,result}
server/ server functions (*.rpc.ts), submit validation, DB service
db/ Drizzle schema and client (one `submissions` table)
config/ questions (gitignored) and the example file
lib/ scoring, guards, schemas, sound engine, helpers
stores/ Zustand stores
hooks/ quiz navigation, sound, hydration helpers
components/ questions, summary, leaderboard, quiz, layout, shadcn
scripts/seed.ts inserts 10 fake submissions with random answers
You need Node 22+, pnpm and a Postgres database (Neon).
pnpm install
echo 'DATABASE_URL=postgres://...' > .env
cp src/config/questions.data.example.ts src/config/questions.data.ts
pnpm db:push # create the submissions table
pnpm dev # http://localhost:3000Other scripts:
| Script | Does |
|---|---|
pnpm build / pnpm preview |
Production build into .output/ and a local preview |
pnpm test |
Vitest (scoring tests) |
pnpm check |
Biome lint and format check |
pnpm db:studio |
Drizzle Studio |
docker build --build-arg QUESTIONS_DATA_TS="$(cat src/config/questions.data.ts)" -t bfq .
docker run -p 3000:3000 -e DATABASE_URL=postgres://... bfq