Web Development/3 min read
Turning a task into a checklist with Gemini
How I built the YallaHustle checklist API in Node.js and Express, which asks Google's Gemini for a structured JSON checklist per task, with per-user daily limits and defensive parsing of the model's output.
YallaHustle helps people get things done. One of its features: you describe a task, and the app breaks it down into a checklist you can tick off. This post is about the small backend behind that feature, a Node.js and Express API that asks Google's Gemini to write the checklist.
One endpoint
The whole API is essentially one route:
POST /generate-checklist
{ userId, taskId, title, description }It returns a JSON object with a checklist array. The server is deliberately thin: it validates the input, checks the limits, builds a prompt, calls Gemini, and makes sure what comes back is valid JSON before returning it.
Prompting for structure, not prose
A chat model's default is to write friendly paragraphs. An app needs data. So the prompt is explicit about the role, the input and the output format:
- the model is a productivity assistant that creates detailed checklists,
- it gets the task's title and description,
- it must answer in valid JSON with a key
checklistthat maps to an array of items, - and it must answer in the same language as the task.
That last line is small but important. YallaHustle users don't all write in English, and a Dutch or Arabic task should get a Dutch or Arabic checklist without the app having to detect the language itself.
Never trust the model's output
Even with a clear prompt, a model sometimes wraps its JSON in a Markdown code fence, adds a sentence before it, or stops halfway. So the server treats the response as untrusted input and checks every step:
- Did Gemini return any candidates at all?
- Does the first candidate contain text?
- After extracting the JSON part from the text, is it long enough to be complete?
- Does it parse as JSON?
Each failure returns a clear 500 error with the raw output attached, which made debugging prompt problems much easier than a generic "something went wrong". Only a response that passes all four checks reaches the app.
Limits: AI calls cost money
Every call to Gemini has a cost, and a checklist button is easy to tap repeatedly. So the API enforces two daily limits per user:
- at most 3 generations per task per day, so you can regenerate a checklist you don't like, but not endlessly;
- checklists for at most 5 different tasks per user per day.
Both reset automatically at midnight: the counters are keyed by the current date, so a new day simply starts with empty counts. Exceeding a limit returns HTTP 429 Too Many Requests with a message the app can show.
Deployment
The server is a standard Express app with a Procfile and a vercel.json, so it can run on a classic Node host or as a serverless function on Vercel. The Gemini API key comes from an environment variable through dotenv and never reaches the client.
Looking back
- In-memory limits don't survive restarts or scale out. The counters live in a JavaScript object, so a restart resets everyone's limits, and two server instances would each keep their own counts. Redis or a database table is the right place for this.
- Ask for JSON properly. Gemini now supports a JSON response mode and response schemas. Using that instead of asking nicely in the prompt would make most of the parsing checks unnecessary.
- Authenticate the caller. The
userIdcomes from the request body, so anyone could pretend to be someone else. It should come from a verified session or token.
Small as it is, this API taught me the core of building on top of LLMs: ask for structure, validate everything, and put a limit on anything that costs money.
Blog / Keep reading
Related articles.
Web Development/4 min read
CourseCraft, a course website from a folder of videos
A zero-dependency Node.js app that turns course folders into a website.
Web Development/4 min read
Building a real-time "guess 80% of the average" game
A Socket.io multiplayer game inspired by Alice in Borderland's Balance Scale.
Web Development/3 min read
A podcast studio CMS with a remote-controlled teleprompter
Vue, Symfony, Electron and one small WebSocket server for a podcast studio.