← All articles

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.

Mohamad.B Alhakim

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:

text
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 checklist that 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:

  1. Did Gemini return any candidates at all?
  2. Does the first candidate contain text?
  3. After extracting the JSON part from the text, is it long enough to be complete?
  4. 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 userId comes 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.

Turning a task into a checklist with Gemini - Mohamad Bashar Alhakim