## How We Built a Travel Deal Aggregation Platform: Architecture Overview
Building [letsjourney.info](https://www.letsjourney.info/) required solving a set of engineering problems that are common to any content-heavy platform with a live data layer - but with the specific constraints of travel data: pricing that changes by the hour, affiliate APIs with inconsistent schemas, and a content hierarchy deep enough to make naive URL generation a performance problem.
This is a high-level overview of the architecture decisions that power the platform.
## The Two-Layer Architecture
The platform separates into two distinct layers with different performance requirements and different update cadences.
**The editorial layer** — destination guides, hotel reviews, trip ideas, destination hierarchy — changes infrequently and is served from pre-rendered or cached templates. Response time requirements are standard.
**The commercial layer** — [travel deals](https://www.letsjourney.info/deals/), [hotel offers](https://www.letsjourney.info/deals/hoteldeals/), [vacation packages](https://www.letsjourney.info/deals/tours-deals/), [travel coupons](https://www.letsjourney.info/coupons/) — changes continuously. Deal pricing updates across 100+ affiliate providers on cadences ranging from real-time to nightly. This layer requires a pipeline architecture that separates ingestion from display.
Mixing these layers was the first mistake we corrected. When live deal data and static editorial content share the same render cycle, a slow affiliate API response blocks the entire page. Separating them means editorial content renders at editorial speed regardless of the commercial data layer's state.
## Deal Ingestion Pipeline
Each affiliate network has a different opinion about data format, update frequency, and authentication. The ingestion pipeline normalizes across them.
```
Affiliate APIs → Raw Ingestion → Validation → Normalization → Staging Store → Display Layer
```
Key design decisions:
**Raw storage before normalization.** We store the original API response alongside the normalized record. When a normalization bug surfaces in production — and it will — raw storage means you can re-process historical data without re-fetching from the API.
**Validation gate between staging and display.** A deal card displaying a price that has changed since ingestion erodes trust faster than almost anything else. Every record passes a validation step before it moves from staging to the live display layer. Stale records fall out of inventory rather than reaching the front end.
**Freshness timestamps, not publication dates.** The [airline deals](https://www.letsjourney.info/deals/airlinedeals/) and [hotel deals](https://www.letsjourney.info/deals/hoteldeals/) displayed on the platform carry a last-confirmed timestamp, not a creation date. A deal created six months ago and re-validated this morning is current. A deal created this morning and not yet re-validated is not.
## Coupon Validation
[Travel coupons](https://www.letsjourney.info/coupons/) present a specific reliability problem: automated aggregation is fast but produces expired codes at a rate that damages user trust. We chose manual verification over automation for the coupon layer.
Every code is tested at the point of checkout for the relevant provider before listing. Expired codes trigger removal within 24 hours of detection. The [Qatar Airways premium coupon section](https://www.letsjourney.info/brands/qatar-airways/) uses the same validation standard with additional destination-specific curation.
The tradeoff is throughput — manual verification scales differently than automated scraping. The benefit is that codes published on the platform work when users apply them.
## Destination URL Architecture
The platform's destination hierarchy runs six levels deep:
```
/{global-region}/{global-subregion}/{country}/{local-region}/{local-subregion}/{city}/
```
Generating this URL dynamically at request time — walking the parent chain in the database for each render — produces N queries per page load. At any meaningful traffic volume, this is not viable.
The solution is pre-computation at write time. When a destination node is saved, the full path, hierarchy depth, and breadcrumb data are computed once and stored directly on the record. At request time, the lookup is a single indexed field read regardless of hierarchy depth. The [Mexico destination hub](https://www.letsjourney.info/destinations/americas/north-america/mexico/) and the [full destinations library](https://www.letsjourney.info/destinations/) both resolve in constant time.
## What This Enables
The architecture above supports the platform's core promise: deal data that is accurate when displayed, editorial content that loads independently of the commercial layer, and destination URLs that are semantically meaningful without being computationally expensive.
[Browse the full deal inventory](https://www.letsjourney.info/deals/) — [flights](https://www.letsjourney.info/deals/airlinedeals/), [hotels](https://www.letsjourney.info/deals/hoteldeals/), [packages](https://www.letsjourney.info/deals/tours-deals/), [cruises](https://www.letsjourney.info/deals/cruise-deals/), [car rentals](https://www.letsjourney.info/deals/car-rental-deals/), [insurance](https://www.letsjourney.info/deals/insurance-deals/), and [verified coupons](https://www.letsjourney.info/coupons/) — to see the output of the pipeline described above.