ItinGen: The Travel Planner That Turns Gemini Into a Structured API

A small React and Express app shows the real challenge of AI products: not generating text, but forcing it into a schema a frontend can trust.

7 min read • View on GitHub • More from ishuee

A wide editorial scene shows a traveler at a desk facing a mechanical press that transforms scattered trip notes into a neatly bound schedule. The image explains the article’s core idea: the app does not chase inspiration, it converts loose intent into structured output the UI can trust.
ItinGen’s real trick is not travel inspiration. It is containment, where free-form intent becomes a predictable itinerary shape.
Key Takeaways

A Travel Planner That Refuses to Ramble

ItinGen starts with a simple promise: describe a trip, get back an itinerary. The interesting part is that the app does not treat Gemini like a conversational partner. It treats it like a service that must return a shape the frontend can render without guessing.

That changes the whole product. The goal is not a clever paragraph about Paris. The goal is valid, structured output with fields the UI can trust, even when the user’s request is vague, budget-sensitive, or underspecified.

A simple itinerary generator.

ishuee, Project Creator/Maintainer · ishuee/ItinGen README

The Secret Is in the Prompt, Not the Model

The core move lives in backend/utils/promptBuilder.js. Instead of asking the model to be creative and hoping for the best, ItinGen injects constraints, fields, and output rules up front. The prompt reads less like a suggestion and more like a contract.

export function buildPrompt({ destination, budget, travelType, transportPreference, currency }) {
  return `You are a professional travel planner.
Return ONLY valid JSON.
Do not wrap the response in markdown.

User trip:
- Destination: ${destination}
- Budget: ${budget} ${currency}
- Travel style: ${travelType}
- Transport preference: ${transportPreference}

Required JSON schema:
{
  "tripSummary": "string",
  "hotelOptions": [
    { "tier": "Budget|Mid-range|Luxury", "name": "string", "why": "string" }
  ],
  "days": [
    {
      "day": 1,
      "morning": "string",
      "afternoon": "string",
      "evening": "string",
      "food": "string"
    }
  ]
}`;
}

The pipeline matters more than the model. ItinGen validates, shapes, parses, and only then renders, which is how an LLM starts to behave like backend infrastructure.

The prompt also does the budget steering. Rather than asking for a generic plan, it demands hotel tiers, day parts, and expense-aware suggestions. That is the difference between a chatbot response and a product output.

How the Request Becomes an Itinerary

The flow is straightforward on paper. TripForm.jsx collects the inputs, MainControllers.js checks the request, and GeminiService.js calls the model. The value is in what happens between those steps: validation, parsing, and the insistence on a shape that can be rendered immediately.

// controller flow, simplified
const { destination, budget, travelType, transportPreference, currency } = req.body;
if (!destination || !budget || !travelType) {
  return res.status(400).json({ error: 'Missing required trip details.' });
}

try {
  const prompt = buildPrompt(req.body);
  const raw = await generateItinerary(prompt);
  const itinerary = JSON.parse(raw);
  return res.json(itinerary);
} catch (error) {
  return res.status(error.statusCode || 500).json({ error: error.message });
}

That is a familiar backend pattern, but it matters more here because the upstream system is probabilistic. The app is not assuming the model will behave. It is building a narrow lane and putting guardrails on both sides.

Why Budget-Aware Output Matters

Most itinerary generators stop at suggestions. ItinGen goes further by asking for budget-aware hotel tiers and practical choices that match the trip profile. That makes the output usable for a real planning session, not just fun to skim.

A tight close-up shows a handwritten trip brief being pressed into a rigid JSON mold. On the other side, orderly slots for day parts and hotel tiers snap into place. The image explains how the app turns loose language into schema-first output.
Prompt engineering here is not about vibe. It is about forcing loose intent into a predictable structure.
ApproachInput styleOutput structureReliabilityBudget sensitivityFrontend friendliness
ItinGenForm fields plus backend validationStrict JSON with itinerary sectionsHigh for a small app because the prompt is constrainedBuilt in through prompt variablesVery high because the UI gets predictable data
Generic AI itinerary generatorOpen-ended chat promptOften prose first, structure secondVariable, depending on prompt disciplineUsually shallow or inconsistentMixed, because the frontend may need cleanup
Traditional travel platformManual search and booking workflowUser assembled, not model generatedHigh, but not automatedStrong through filters and pricingHigh for booking, lower for fast itinerary synthesis

The contrast is simple. Traditional platforms optimize for search and booking. Generic AI tools optimize for surprise. ItinGen optimizes for shape.

Graceful Failure Is Part of the Product

The backend does not hide from model failure. In GeminiService.js and the controller layer, quota errors and service overload are translated into human-readable messages instead of a dead end. That is a small detail with a big product effect.

try {
  return await callGemini(prompt);
} catch (error) {
  if (error.status === 429) {
    throw new Error('This demo has hit its daily limit. Please try again later.');
  }
  if (error.status === 503) {
    throw new Error('Gemini is experiencing high demand. Please retry shortly.');
  }
  throw new Error('Something went wrong while generating the itinerary.');
}

That kind of handling is what separates a demo from a product-minded prototype. Users can forgive limits. They do not forgive silence or broken UI states.

What ItinGen Is Really Compared With

ItinGen is not trying to out-platform TripIt or out-market a commercial travel assistant. It belongs to a different class of tools: small AI wrappers that prove a pattern. The comparison is useful because it shows where the project is deliberately narrow.

CategoryWhat it optimizes forWhat ItinGen does differently
Commercial travel plannerBooking, organization, and broad travel utilityFocuses only on generation and output discipline
Generic AI trip generatorOpen-ended conversation and noveltyForces schema-first responses that a frontend can trust
DIY prompt-in-chat workflowSpeed of experimentationMoves the prompt into a backend contract with validation and parsing

That narrowness is the point. ItinGen is not a destination app. It is a lesson in how to keep an LLM from leaking complexity into the rest of the stack.

Why This Tiny Stack Is Worth Studying

The stack is small, but the lesson is durable. Most useful AI products are not won by bigger models. They are won by better contracts around the model, clearer boundaries, and enough backend discipline to make probabilistic output behave like software.

That is why ItinGen is worth a look. It shows the current AI wrapper pattern in a clean, legible form: React for input and display, Express for control, Gemini for generation, and prompt design as the real product surface.