This is a submission for the GitHub Finish-Up-A-Thon Challenge
What I Built
I rebuilt Cookify from an abandoned side project into a complete AI-powered recipe workspace — one that takes whatever ingredients you actually have in your kitchen and returns a real, structured, step-by-step recipe personalized to your dietary preference, complete with a dish photo and a downloadable PDF.
The original was a basic React prototype. It had a static ingredient list and a "Generate" button that called the Gemini API — but with no authentication, no persistence, no structure to the AI output, and no error handling. The recipe text came back as a raw unformatted string. There was no way to revisit what you'd made. It was a demo, not a product.
The current version is a Next.js App Router application built around a deliberate, end-to-end flow: login → select diet → pick ingredients → generate → save → manage.
-
Structured AI output — Gemini is called with a
responseSchemaconfig that enforces a typed JSON response every time: recipe name, ingredient list with quantities, numbered steps, estimated cook time, and serving size. No raw string parsing, no brittle regex — the output is always valid and immediately renderable. - User authentication — Google OAuth via NextAuth.js. Every recipe is scoped to the user who generated it, stored in MongoDB under their profile.
- Personal recipe book — all generated recipes are auto-saved. Users can browse their full history with pagination, delete recipes they no longer want, and the UI reflects real state, not stale cache.
- Dynamic dish photography — on every generation, the Pexels API is queried in parallel using the dish name to fetch a relevant, high-quality image. The image URL is saved alongside the recipe data.
- PDF export — any saved recipe can be downloaded as a clean, printable PDF built with jsPDF, with page-break logic so long recipes don't overflow mid-step.
- Profile editing — users can update their display name, saved back to MongoDB instantly.
- Responsive, accessible UI — built with Tailwind CSS and Shadcn/UI, works cleanly on mobile and desktop.
The real thing I finished wasn't more features. It was correctness. Structured output so the AI never breaks the UI. Authentication so recipes belong to someone. Pagination so loading 50 saved recipes doesn't freeze the browser. Error boundaries so a failed Pexels fetch doesn't kill the whole generation. That's the difference between "it works in the demo" and something you'd actually use every day.
Demo
GitHub: github.com/codewithmanohar/cookify
Live Demo: https://trycookify.vercel.app/
The full flow in four steps:
1. Choose your dietary preference
2. Pick the ingredients you have on hand
3. Your recipe — structured, photographed, ready to cook
4. Your personal recipe book — paginated, deletable, exportable
The Comeback Story
Cookify started with a real problem I have personally: I open my fridge at 7pm, see onions, tomatoes, paneer, and have absolutely no idea what to make. The original prototype proved the idea worked — Gemini could generate a recipe from a list of ingredients. But the project had stalled at proof-of-concept. The original code had no auth, no database, no structure to the AI response, and no way to ever revisit what you'd generated.
For the comeback, I kept the core idea and rebuilt everything around the parts that make it actually usable.
Where it was before:
The original generate API route called Gemini with a plain text prompt and rendered whatever string came back. Sometimes it was formatted nicely. Sometimes it started with a preamble. Sometimes it was missing the ingredient quantities. There was no way to know what shape the response would be until runtime. The frontend did string splitting and guessing to try to render sections — and it broke constantly.
There was also no persistence at all. Every page refresh lost the recipe. Users had to copy-paste if they wanted to save anything.
What I finished:
The single biggest architectural fix was switching Gemini to use responseSchema. I defined a strict JSON contract — recipeName, ingredients as an array of objects with name and quantity, steps as a numbered array, cookTime, servings — and enforced it on every API call. The UI now maps directly over a typed response. There is no parsing. There is no guessing. The recipe either comes back in the right shape or the error boundary catches it.
I added full MongoDB persistence with Mongoose. Every generated recipe is saved with the user's ID as a foreign key the moment it's returned from the API, so even if the user closes the tab immediately, it's there. I built paginated recipe retrieval so the "My Recipes" page loads in batches of 8 — no more full-collection reads on every visit.
The PDF export was broken in the original — long recipes ran off the bottom of the page with no continuation. I rewrote the jsPDF layout logic using a running yOffset tracker that checks remaining page height before rendering each element and inserts a doc.addPage() call when needed. Recipes now paginate correctly regardless of length.
I also added recipe deletion with a confirmation dialog, profile name editing, and react-toastify notifications so every action — save, delete, export, error — has visible, non-blocking feedback.
The project went from an unfinished proof-of-concept into something with typed contracts, real data persistence, structured AI output, auth-scoped data, and a UI that gives honest feedback at every step.
My Experience with GitHub Copilot
GitHub Copilot wasn't just autocomplete on this project — it was how I moved from "I know what needs to be done" to actually doing it without losing the thread across multiple days of work.
Structured output was the hardest problem. Copilot pointed me at the right solution.
When I described my problem — "Gemini keeps giving me inconsistently formatted recipe text and my frontend is doing too much string parsing" — Copilot didn't suggest better parsing. It suggested I look at Gemini's responseSchema parameter and structured output mode, which I hadn't used before. That was the right answer. I asked it to write the schema definition for my recipe structure, and it produced a clean Zod-style schema that I adapted into the Gemini config. The unformatted string problem disappeared entirely.
fix. One word.
Midway through building the recipe deletion flow, I introduced a circular dependency between two API route files that broke the dev server. Instead of writing a detailed bug report, I typed fix. Copilot traced the import chain, resolved it, and then — without me asking — pointed out that my old recipe-draft.json test file would cause issues if I committed it and helped me add it to .gitignore. It didn't just patch the error; it stopped the next problem before it happened.
It held the project context I couldn't hold in my head.
Working on a multi-file Next.js project across several days means constantly re-explaining context to yourself. Copilot retained the data model, the naming conventions, the API route structure, and the Zustand store shape between sessions. When I came back to add profile editing after two days away, I described what I wanted and it produced code consistent with the patterns already in the project — same error handling style, same response shape, same Mongoose update pattern. I didn't have to re-read my own codebase before continuing.
It found the pagination edge case I hadn't thought about.
When I asked Copilot to help me build paginated recipe fetching, it wrote the basic skip() and limit() query — but then flagged something I hadn't considered: if a user deletes a recipe while on page 2, the page offset can return an empty array without indicating whether the user has reached the actual end of their data or just hit a gap from a deletion. It suggested returning a hasMore boolean from the API alongside the data, calculated from whether the total document count exceeds the current offset. That's the kind of edge case I'd have discovered only after a user reported it.
It pushed back when my architecture was off.
At one point I was storing the generated recipe in Zustand client state and planning to only save it to MongoDB when the user explicitly clicked "Save." Copilot flagged that this created a data-loss risk — if the user navigated away or the session expired before saving, the recipe was gone. It suggested auto-saving on generation and letting the user delete later if they didn't want it. That reframe is now the entire architecture of the recipe book. Auto-save on generation turned out to be the right product decision, not just a technical one — users don't have to think about whether they saved.
The pattern across all of it: I described what I was trying to accomplish, often loosely, and Copilot filled in the implementation, the edge cases, and occasionally the better approach. The product decisions stayed mine. The correctness details were collaborative.
Top comments (0)