I Built an AI Homework Generator App
The Whole App Is One Prompt
School homework is boring. The schools sends out curriculum worksheets with little thought for making it interesting, let alone personalised.
So I built Homework Creator. You give it a child’s name, year group, and what they are into. It gives you about thirty minutes of homework built around those interests, plus a separate answer sheet.
It is live, you can try it for free now. Or you can deploy it yourself - the code is on GitHub. The interesting bit is how little there is.
The app is a form wrapped around one API call
There is no orchestration here. No agent, no tool calling, no RAG, no vector store. A React form collects five fields, a function turns those fields into a string, that string goes to a chat completion, and the markdown that comes back gets rendered. That is the entire product.
The bit that took the time was the prompt itself.
Each subject is a config entry. Adding a subject means adding an object:
maths: {
label: 'Maths',
minutes: 10,
instructions: `
### Maths (~10 minutes)
Include TWO tasks:
1. **Main Problem** — a multi-step word problem or real-world application,
slightly above grade level. Use an interesting context (sports stats,
world records, space, money, the child's interests). 2–3 parts maximum.
2. **Puzzle or Pattern Challenge** — a short number puzzle, sequence, or
logic problem. Should feel like a game, not a drill.`,
answersNote: 'Show full worked solutions for each part. Flag if this is above grade level.',
},
buildPrompt picks the selected subjects, sums their minutes, concatenates the instruction blocks, and wraps the lot in tone rules and an output format. Four subjects, four config objects, about a hundred lines including the prompt template.
The tone rules make all the difference:
- Fun and energetic — write like a cool teacher, not a worksheet printer
- Avoid dry drills and rote exercises — every task should feel like a
challenge or puzzle
- Real data and big numbers are always a win
This is the point I keep finding with small AI apps. The prompt is the differentiator. The model, the framework, and the hosting are all interchangeable.
Two audiences, one API call
The homework needs answers, but we don’t want the child to see them. The obvious build would be two calls: generate the homework, then send it back and ask for answers.
But two calls will be non-deterministic and might drift. So I did it in one. The prompt asks for two clearly marked sections, and then we split the response:
export function parseOutput(raw) {
const section2Markers = [
/#{1,3}\s*SECTION 2/i,
/#{1,3}\s*ANSWERS/i,
/^# Answers/im,
]
for (const marker of section2Markers) {
const match = raw.search(marker)
if (match !== -1) {
return {
homework: raw.slice(0, match).trim(),
answers: raw.slice(match).trim(),
}
}
}
return { homework: raw.trim(), answers: null }
}
Half the cost, half the latency, and the answers are guaranteed to match the questions.
The answers view has an optional password. This is just a SHA-256 hash held in React state, which means it clears on refresh and would not survive anyone with dev tools open. It is a lock against a nine year old, not against an attacker.
The infrastructure exists to hide the Azure key
I originally wanted this on GitHub Pages. Static site, free.
Unfortunately this wasn’t an option. GitHub Pages serves files and nothing else, so there is no server-side anything, which means an Azure key in the front end ends up in the JS bundle where anyone can read it. There is no way around this. If your app calls a paid API and has no server, your key is public.
So it runs on Cloudflare Pages instead. Same free tier, same git-push-to-deploy, but Pages also runs Functions at the edge. functions/api/generate.js holds the Azure AI Foundry key as a server secret, and the browser calls that instead of calling Azure.
Once you expose a public endpoint that spends money on your behalf, you need to lock it down. Here are the credential checks, in order:
Content type and origin. Rejects anything not posted as JSON from the site’s own origin.
Cloudflare Turnstile. An invisible challenge, verified server side with a single-use token. Checks that there is a real user.
Per-IP rate limits in KV. Three requests a minute, thirty a day.
Input bounds. The prompt must be a string between 20 and 8,000 characters.
Fixed model parameters. Temperature and
max_tokensare set server side. The client sends a prompt and nothing else.
The final layer of defense is a spending cap on the Azure resource.
Sharing with no database
Once a homework set is generated you can share it.
The homework text is gzipped, base64url encoded, and dropped into the URL fragment:
export async function buildShareLink(text) {
const stream = new Blob([text]).stream()
.pipeThrough(new CompressionStream('gzip'))
const buffer = await new Response(stream).arrayBuffer()
const encoded = toBase64Url(new Uint8Array(buffer))
const url = new URL(window.location.href)
url.hash = 's=' + encoded
return url.toString()
}
Opening the link decodes it locally - no database, no record of who generated what, no data retention question to answer about other people’s children. Also, the answers are never included in the link. CompressionStream is native in every current browser, so this is about fifteen lines - no dependencies.
Tips for Building an App Around a Prompt
Put your effort into the prompt. At this size, the prompt is the application.
Ask for structured output in one call and parse it. The second call is usually avoidable.
If your front end calls a paid API, you need a server, even a tiny one. Cloudflare Pages Functions cost nothing and took an afternoon.
Rate limit before you launch. The bill is a bad way to find out.
Check whether a browser primitive already solves your problem before you add a database.
Try it. (It’ll be up until I take down the agent for whatever reason).
It works best if you are specific about interests. “Football” gets you generic sums about goals. “Man Utd” gets you something much more engaging - which is the whole point.
The code is at github.com/stuartdotnet/homework-creator. The README has the full Cloudflare and Foundry setup if you want to run your own.
If you fork it, or you find a subject config that produces better output than mine, let me know!


