AI-interview-coach: AI Interview Coach: When the LLM Becomes a Typed Backend

How this repo turns a resume or job description into structured guidance, a day-by-day prep plan, and a polished PDF resume without relying on brittle prompt parsing.

8 min read • View on GitHub • More from Miners03

A resume sheet and job description moving through a mechanical press, then emerging as a structured interview dossier and a polished resume PDF. The scene explains how the app turns messy candidate input into deterministic outputs by forcing the model through a schema-shaped middle layer.
The project’s trick is not better chatting. It is better shape.
Key Takeaways

The LLM is not chatting. It is filling a schema.

That is the real story here. The app takes a resume, a self-description, or a job description and forces the model to return a structured interview report, not a rambling answer. In practice, that means the model behaves more like a backend primitive than a conversational UI.

The important architecture choice is not the model. It is the contract around the model.

const interviewReportSchema = z.object({
  matchScore: z.number().min(0).max(100),
  skillGaps: z.array(z.string()),
  technicalQuestions: z.array(z.string()),
  behavioralQuestions: z.array(z.string()),
  preparationPlan: z.array(z.object({
    day: z.number(),
    task: z.string(),
  })),
});

const jsonSchema = zodToJsonSchema(interviewReportSchema);
const report = await generateContent({
  model: 'gemini-3-flash-preview',
  schema: jsonSchema,
});

That schema is the product. Once the response is forced into known fields, the app can score fit, surface gaps, generate questions, and render a preparation plan without guessing what the model meant. The output becomes database-ready instead of prompt-ready.

The input can be messy. The output stays clean.

The backend is built to absorb ugly real-world input. A user can upload a PDF resume or paste a self-description, and the server handles the file boundary with multer, extracts text with pdf-parse, and then normalizes that text into a prompt. The whole point is to make the input path forgiving without letting the output drift.

LayerGeneric chatbot flowAI Interview Coach
InputFreeform prompt textPDF resume, self-description, or JD
Model outputParagraphs and guessworkSchema-validated JSON
Downstream useManual copyingUI, roadmap, and PDF generation
Failure modeHard to parseEasy to validate
Product shapeConversationPipeline

That difference matters because the messy part is upstream, not downstream. The app does the painful work at the edge, then preserves a clean shape for everything after it.

A close-up of a sorting machine that accepts a crumpled resume and a job description card, then separates skills, gaps, and questions into distinct trays. The image explains how the app extracts messy inputs and sorts them into clean structured outputs.
The app behaves like a sorter, not a chatbot.

The best output is not advice. It is an artifact.

The most useful move in the repo is the one-step conversion from structured interview insight to a tailored resume. The AI generates HTML, and Puppeteer turns that HTML into a PDF. That matters because the user does not just get suggestions. They get something they can download, send, and actually use.

const html = await generateTailoredResumeHTML(profile, jobDescription, report);
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(html, { waitUntil: 'networkidle0' });
await page.pdf({ format: 'A4', printBackground: true });

The same logic powers the prep roadmap. It is not a blob of advice buried in a chat transcript. It is a day-by-day object that the frontend can render into a real workflow, which makes the product feel much more deliberate than a typical AI wrapper.

The frontend is built around the shape of the data.

The React side is organized around domains rather than generic screens. Authentication lives apart from interview logic. A custom hook owns the interview flow. Protected routes keep the experience coherent once the token is in place. That structure mirrors the backend contract: clear state in, clear state out.

Frontend concernHow it shows upWhy it matters
AuthProtected routes and token stateKeeps interview data behind a session boundary
Interview flowCustom <code>useInterview</code> hookSeparates fetch logic from UI rendering
StateFeature-based contextMakes report, loading, and roadmap states explicit
RenderingRoadmap and report sectionsMatches the schema instead of fighting it

That is the difference between a demo and a product. The UI is not improvising around model output. It is built to expect the output shape from the start.

Security and validation are doing real work here.

This repo is better than many AI demos because it does not ignore the boring parts. JWT auth is in place, logout blacklisting exists, and file uploads are handled through a controlled middleware path. Those are not flashy features, but they are the difference between a toy and software that can survive users.

ConcernAI demo patternThis repo
Session controlOne token, no revocationJWT plus blacklist on logout
File handlingTrust the uploadMulter boundary plus parsing step
Model outputParse later if neededValidate immediately with Zod
Data flowLoose and conversationalConstrained and repeatable

The validation story is especially strong. If the model misses the schema, the app does not have to guess. It can reject, retry, or surface a precise failure. That is how you make generated output trustworthy enough to build on.

What this repo gets right that generic AI interview tools miss.

Generic interview tools often optimize for conversation. This one optimizes for completion. It is closer to a workflow engine than a coach, and that is why it feels practical. It can be self-hosted, it keeps the candidate’s data closer to the application, and it produces reusable artifacts instead of just reassurance.

QuestionGeneric interview toolAI Interview Coach
What do I get?A chat sessionA structured report and a PDF
How predictable is it?LowHigh
Can I reuse the output?Usually notYes, as UI data and documents
Is the model the product?Often yesNo, the output shape is the product
Does it feel shippable?SometimesYes

That is the central lesson. The best GenAI apps are often the least chatty ones. They narrow the model’s job, validate the result, and then turn that result into something people can actually carry forward.