Project · ★★★ · Advanced
Quiz Maker
Paste your class notes, or type a topic, and get a ten-question quiz you can send to friends with a link. They play it, see why each answer is right, and fight for the top of the leaderboard. You build the part that talks to a language model, and you build it the way real apps do: the model is called from your server, with no key anywhere in the browser for someone to copy.
You need
- Set up your computer, so you have a terminal, git and Node.
- Claude Code or Codex, set up and logged in: Get Claude Code or Get Codex.
- How to work with an agent. Read it once. It saves hours.
- Keep data, especially the part on KV.
- A free Cloudflare account (dash.cloudflare.com/sign-up). The free plan covers everything here. Workers AI has its own free daily allowance; check the Workers AI pricing page for the current amount.
- A free GitHub account, if you want to publish it: Save it on GitHub.
What you'll learn
- Calling a model from server code, through a binding or a secret, never from the browser.
- Asking for strict JSON, then validating it, because a model can return anything.
- KV: fast key and value storage, its daily write limit, and what "eventually consistent" means for a leaderboard.
- Server-side checking, so the answers are not sitting in the page source.
- Protecting a free allowance with a secret password.
What this shows the agent can do
- Write a prompt, the code that calls it, and the checks that catch bad output.
- Explain why a design is unsafe (a key in the browser) and move the code to the right place.
- Point out a limit of its own design, like two scores overwriting each other, and name the fix.
The brief
Make a new folder for the project. Copy the brief below into a file called BRIEF.md in that folder, or download BRIEF.md. Open the agent in the folder and say:
Read BRIEF.md and start with milestone 1. Stop after each milestone so I can check it.
# Quiz Maker — turn any text into a quiz you can share (★★★)
Build a quiz maker. Paste a page of notes, or type a topic like "the water cycle", and a minute later there is a ten-question multiple-choice quiz. A Cloudflare Worker sends the text to a language model on Workers AI, checks that the answer is valid quiz JSON, and saves the quiz in KV under a short code. Send the link to friends: they play it, see their score and an explanation for each answer, and get on that quiz's top-10 leaderboard. The real lesson is where the model call lives: in server code, behind a binding or a secret, and never in the browser with a key anyone can copy.
## Done looks like
- The maker screen: a big text box (up to 8,000 characters) or a topic box (up to 100), a difficulty choice (easy, medium, hard), a maker password box and "Make quiz".
- While it works, the page says what is happening. Within about a minute it shows the new quiz's link, like `/q/H7KM2P`.
- Every quiz has exactly 10 questions, each with 4 choices, one right answer and a one-sentence explanation.
- The play screen shows one question at a time. After each answer it shows right or wrong and the explanation, then "Next". The end screen shows the score out of 10.
- Answers are checked by the Worker. The browser never receives the answer key before the player answers, so reading the page source does not help.
- After the last question, a player can enter a name (up to 12 characters) and, if the score makes the top 10, it appears on the quiz's leaderboard.
- Every quiz page says "Written by a model. It can be wrong."
- A wrong maker password gets "Wrong password" and no model call. Playing needs only the link.
- Live at the address `npx wrangler deploy` prints. No console errors.
## Constraints
- One Cloudflare Worker with static assets, a Workers AI binding (`AI`) and a KV binding (`QUIZZES`). Use wrangler. Free plan only.
- The model is called only from the Worker, through the `AI` binding. No API key appears in the front end or in the repo. If you later switch to an outside model API, its key goes in with `npx wrangler secret put` and stays on the server.
- Pick a text model from the Workers AI model list (developers.cloudflare.com/workers-ai/models). Prefer one that supports JSON output. Ask for JSON only, then parse it defensively.
- Validate on the server: exactly 10 questions; each has `q` (up to 200 characters), `choices` (exactly 4 different strings, each up to 80), `answer` (0 to 3) and `explain` (up to 200). Trim and clean the text. If it fails, retry the model call once, then give a clear error.
- Workers AI has a free daily allowance. Protect it: making a quiz needs `MAKER_PASSWORD`, a Worker secret set with `npx wrangler secret put MAKER_PASSWORD`. Cap the input length before calling the model.
- KV keys: `quiz:<code>` holds `{title, difficulty, created, questions}`, written once. Do not store the pasted text, only the questions. `board:<code>` holds the top 10 as `[{name, score, ms}]`.
- KV allows 1,000 writes a day on the free plan and is eventually consistent, so write the board only when a score makes the top 10. Note in a comment that two scores at the same moment can overwrite each other, and that a Durable Object or D1 would fix it.
- Codes: 6 characters from an alphabet with no lookalikes.
- Routes: `POST /api/quiz` (make), `GET /api/quiz/<code>` (questions without answers), `POST /api/quiz/<code>/check` (`{i, choice}` returns right or wrong, the right answer and the explanation), `POST /api/quiz/<code>/finish` (all choices; the Worker counts the score), `GET /api/quiz/<code>/board`.
- Tell the model in the prompt: questions only about the given text or topic, no personal details about real private people.
- Front end: plain HTML, CSS and JavaScript, no frameworks, no build step. No accounts, no tracking. Phone works.
## Milestones
1. **A player with a fixed quiz.** The play screen working from a hard-coded quiz object in the front end. No server yet.
Check: you can play all 10 questions, see an explanation after each, and get a score at the end.
2. **A Worker.** Set up the Worker with static assets serving the player, and `GET /api/health` returning `ok`. Run it locally.
Check: the quiz plays from the local wrangler address and `/api/health` shows `ok`.
3. **First model call.** Add the `AI` binding. `POST /api/quiz` sends a topic to the model and returns the raw text. Show it on a test page.
Check: the topic "volcanoes" returns text that looks like quiz questions about volcanoes.
4. **Valid JSON or a clear error.** Ask for JSON only, parse it, validate every field, retry once, and turn failures into a short message.
Check: ten topics in a row each give exactly 10 valid questions or a clear error, and the page never shows a broken quiz.
5. **Password and go live.** Add `MAKER_PASSWORD`. Deploy. Make a quiz from your phone by pasting class notes.
Check: a wrong password is refused without a model call (nothing new in the Workers AI usage), and the right one makes a quiz from your phone that you can play there and then.
> A quiz now lives only in the tab that made it. The next two keep it under a short code so anyone with the link can play, and keep the answers on the server.
6. **Save and share.** Store the quiz in KV under a code. Serve `/q/<code>`. Move answer checking to the server with the `check` and `finish` routes, and remove answers from what the browser gets. Deploy again.
Check: open the link in another browser and play it; search the page source and network responses for the answers before answering: they are not there.
7. **Leaderboard.** Name entry at the end, top 10 per quiz in KV, shown on the end screen.
Check: three players with different scores appear in the right order, and an 11th lower score does not write to KV.
## Stretch
- "Report a wrong answer" on each question, stored as a count in KV, shown to the quiz maker.
- A timer mode where faster right answers score more, scored on the server.
- Quiz from a web page address: the Worker fetches the page, strips it to text and caps the length.
- Picture questions using the photo-wall approach (R2 plus signed addresses).
- Move leaderboards to a Durable Object so simultaneous scores never collide.
## Leave out
- Accounts, classes, teacher dashboards.
- Any model call or API key in the browser.
- Storing the text people paste.
- Tracking, ads.
Start by reading WRITING.md and the guide chapter on data if you need storage.Milestones
The same milestones as the brief. Tick each one off when its check passes. Do one per sitting if you like. The dot in the list marks the point where you have something that works on its own. Everything after it makes it remember things or work for more than one person. Stop at the dot and use what you built before you go on.
- A player with a fixed quiz. The play screen working from a hard-coded quiz object in the front end. No server yet.
Check: you can play all 10 questions, see an explanation after each, and get a score at the end.
- A Worker. Set up the Worker with static assets serving the player, and
GET /api/healthreturningok. Run it locally.Check: the quiz plays from the local wrangler address and
/api/healthshowsok. - First model call. Add the
AIbinding.POST /api/quizsends a topic to the model and returns the raw text. Show it on a test page.Check: the topic "volcanoes" returns text that looks like quiz questions about volcanoes.
- Valid JSON or a clear error. Ask for JSON only, parse it, validate every field, retry once, and turn failures into a short message.
Check: ten topics in a row each give exactly 10 valid questions or a clear error, and the page never shows a broken quiz.
- Password and go live. Add
MAKER_PASSWORD. Deploy. Make a quiz from your phone by pasting class notes.Check: a wrong password is refused without a model call (nothing new in the Workers AI usage), and the right one makes a quiz from your phone that you can play there and then.
- A quiz now lives only in the tab that made it. The next two keep it under a short code so anyone with the link can play, and keep the answers on the server.
- Save and share. Store the quiz in KV under a code. Serve
/q/<code>. Move answer checking to the server with thecheckandfinishroutes, and remove answers from what the browser gets. Deploy again.Check: open the link in another browser and play it; search the page source and network responses for the answers before answering: they are not there.
- Leaderboard. Name entry at the end, top 10 per quiz in KV, shown on the end screen.
Check: three players with different scores appear in the right order, and an 11th lower score does not write to KV.
Make it yours
Once it works, pick one of these and ask for it as a new milestone.
- "Report a wrong answer" on each question, stored as a count in KV, shown to the quiz maker.
- A timer mode where faster right answers score more, scored on the server.
- Quiz from a web page address: the Worker fetches the page, strips it to text and caps the length.
- Picture questions using the photo-wall approach (R2 plus signed addresses).
- Move leaderboards to a Durable Object so simultaneous scores never collide.
If it goes wrong
The model sometimes returns text around the JSON, and the quiz breaks.
Models add friendly sentences or code fences unless told and checked. Say this to the agent:
Sometimes the quiz breaks because the model adds text around the JSON. Ask for JSON only, strip anything outside the outer braces, validate every field, and retry once before showing an error.
You can see the answers in the browser's network panel.
The question route is sending the whole quiz object. Say this to the agent:
The answers are visible in the network panel. Remove answer and explain from GET /api/quiz/<code> and only send them back from the check route after the player answers.
"AI binding not found" or similar when running.
The binding is missing from the wrangler config, or the name does not match env.AI in the code. Say this to the agent:
The Worker says the AI binding is missing. Show me the wrangler config and the line that calls the model, and make the binding names match.