NextGenTrip — Full-Stack Travel Booking Platform
A production-grade travel booking platform that integrates live hotel and flight inventory via the TBO Travel API, processes real payments through Stripe, and serves 500+ booking sessions per month — built on Next.js SSR and a Laravel REST API backend.
Project Overview
NextGenTrip is a full-featured online travel agency (OTA) platform enabling end-users to search, compare, and book hotels and flights in real time. Built as a commercial product and currently live at nextgentrip.com, the platform competes in one of the most technically demanding product categories — travel booking — where sub-second API responsiveness, payment reliability, and inventory accuracy are non-negotiable.
The core challenge was integrating the TBO Travel API — a third-party B2B travel inventory supplier — into a consumer-facing UX that feels as fluid as booking on Expedia or MakeMyTrip, while keeping the infrastructure lean, maintainable, and extensible by a small team.
The Problem This Solved
The travel industry's existing white-label solutions come with rigid UX constraints, high licensing costs, and no room for custom feature development. The client needed a platform they owned entirely — one that could be modified, scaled, and branded independently without vendor dependency.
Specific challenges this project addressed:
Real-Time Pricing Volatility
TBO API prices change with every request. The platform needed a pricing engine that fetches live rates, caches intelligently, and clearly communicates price freshness to users.
Payment Reliability at Booking Moment
Travel bookings are time-sensitive — inventory can disappear between search and payment. Stripe integration had to handle confirmation webhooks and failed payment recovery gracefully.
Multi-Role Admin Needs
Agents, admins, and customers need radically different data views. A single codebase with robust RBAC was required instead of building separate applications.
Technical Architecture
NextGenTrip is architected as a decoupled full-stack application: a Next.js 14 frontend communicates with a Laravel REST API backend over HTTPS. The two systems share no codebase, enabling independent scaling and deployment.
Backend — Laravel REST API
The Laravel backend serves as the central orchestration layer between the Next.js frontend, the TBO Travel API, Stripe, and MySQL. Key architectural decisions:
TBO API Integration Layer — Laravel Service Class
// app/Services/TBOService.php
class TBOService
{
protected string $baseUrl;
protected string $credentials;
public function searchHotels(HotelSearchRequest $request): Collection
{
// Cache search results for 5 minutes to reduce API calls
$cacheKey = 'tbo_hotels_' . md5(serialize($request->toArray()));
return Cache::remember($cacheKey, 300, function () use ($request) {
$response = Http::withHeaders($this->getAuthHeaders())
->timeout(8)
->post("{$this->baseUrl}/hotels/search", $request->toArray());
return $this->transformHotelResults($response->json());
});
}
public function getHotelPrice(string $hotelCode, Carbon $checkIn): PricingResult
{
// Prices are never cached — always live
$response = Http::withHeaders($this->getAuthHeaders())
->post("{$this->baseUrl}/hotels/prebook", [
'HotelCode' => $hotelCode,
'CheckIn' => $checkIn->format('Y-m-d'),
]);
return PricingResult::fromApiResponse($response->json());
}
}
Frontend — Next.js 14 with SSR
The Next.js frontend uses Server-Side Rendering (SSR) for search result pages, ensuring that hotel and flight listings are included in the initial HTML payload — critical for both SEO indexability and perceived performance. Static pages (marketing, about, FAQs) use Static Site Generation (SSG) for maximum caching efficiency.
The sub-2-second page load target is achieved through: SSR reducing client-side JavaScript execution, AWS S3 + CloudFront CDN serving all images and static assets from edge nodes, and Incremental Static Regeneration (ISR) for destination pages that change infrequently.
Database Design
MySQL schema is designed around the booking lifecycle: searches → prebook_sessions → bookings → payments. Every TBO API response is stored as a JSON snapshot in booking_snapshots, ensuring pricing disputes can always be resolved against the exact data the customer saw at booking time — a legal requirement in travel commerce.
Payment Integration — Stripe
Stripe Checkout is used for the payment flow, with webhook-driven booking confirmation. The booking is created in a pending state when Stripe Checkout is initiated. Only on receipt of a checkout.session.completed webhook event does the Laravel backend confirm the booking with TBO and mark it confirmed. This prevents double-bookings and lost payments on network failures.
Stripe Webhook Handler — Booking Confirmation
// app/Http/Controllers/WebhookController.php
public function handleStripeWebhook(Request $request): Response
{
$payload = $request->getContent();
$sig = $request->header('Stripe-Signature');
$event = Webhook::constructEvent($payload, $sig, config('stripe.webhook_secret'));
match ($event->type) {
'checkout.session.completed' => $this->confirmBooking($event->data->object),
'payment_intent.payment_failed' => $this->handleFailedPayment($event->data->object),
default => null,
};
return response()->json(['received' => true]);
}
private function confirmBooking(object $session): void
{
$booking = Booking::findByStripeSession($session->id);
$booking->update(['status' => 'confirmed']);
// Confirm with TBO API
$this->tboService->confirmBooking($booking->tbo_reference);
// Dispatch confirmation email
Mail::to($booking->customer)->send(new BookingConfirmationMail($booking));
}
Key Technical Challenges and Solutions
Challenge 1
TBO API has a strict rate limit and returns inconsistent response structures depending on the hotel supplier. Naively calling it on every user interaction would breach limits and cause UX failures during peak traffic.
Solution
Implemented a two-layer caching strategy: search results cached for 5 minutes (acceptable price staleness for browsing), while pre-booking prices are always fetched live with a clear "price confirmed at checkout" UI message. Response transformation is handled by a dedicated TBOResponseTransformer class that normalises all supplier-specific field variations.
Challenge 2
Hotel images from TBO come as third-party URLs that are slow, sometimes broken, and cannot be optimized by the application. Using them directly degraded LCP scores significantly.
Solution
Built an image proxy pipeline: on first booking search, hotel images are fetched, resized via a Laravel Intervention Image job, and stored in AWS S3 with CloudFront distribution. Subsequent requests serve the CDN-optimized image instead of the TBO source URL. This reduced image-related LCP from 4.2s to under 1s.
Challenge 3
Managing authentication state across a decoupled Next.js + Laravel architecture with multiple user roles required a secure, stateless approach that worked with both SSR and client-side navigation.
Solution
Implemented Laravel Sanctum token authentication with HTTP-only cookies for the user-facing frontend, and a separate Bearer token mechanism for the admin panel. SSR pages receive user context via a server-side token validation call before rendering, ensuring no authenticated content is served to unauthorized users even on first load.
Multi-Role Admin Panel
The admin panel is built as a separate React SPA served from the same Next.js application under a protected route. Three distinct roles define what data and actions are accessible:
Super Admin
Full platform access: booking management, revenue reports, user management, API configuration, pricing rule overrides, and system health monitoring.
Travel Agent
Restricted to their assigned customer pool: view and manage bookings, process manual payments, generate booking vouchers, and communicate with customers.
Customer
Personal booking history, itinerary downloads, cancellation requests, and profile management — all through a consumer-facing dashboard separate from the admin panel.
What I Learned
Building a production travel platform crystallised several lessons about third-party API integration at scale. The most important: never trust external API response structures to be consistent. Building a normalisation layer between TBO's raw responses and your application's domain models is not optional — it's what makes the application maintainable when the API adds new suppliers with different data shapes.
The second lesson: payment webhook reliability matters more than payment initiation. I initially built a simpler return-URL based confirmation. After seeing two bookings fail during testing due to users closing the browser before redirect, I rebuilt the entire confirmation flow around webhooks. This added a week of work but made the payment system genuinely reliable.
Project Info
Tech Stack
language
framework
other