A personal flight price tracker that compares flights from multiple sources, visualizes routes on an interactive map, and sends price change digests to your email.
- Multi-source comparison: Amadeus (2,000 free searches/month) + Google Flights via SerpAPI
- Source toggle: Enable/disable individual data sources; results filter client-side without re-fetching
- Interactive map: Great-circle flight paths rendered with deck.gl + MapLibre GL (free, no API key)
- Price tracking: Save searches for daily automated price monitoring (runs at 06:00 UTC)
- Email digest: On-demand or scheduled full price snapshot with change indicators (▼ green / ▲ red)
- Airport autocomplete: Instant search across 4,500+ airports (OurAirports CC0 dataset)
| Layer | Choice |
|---|---|
| Frontend | React + Vite + TypeScript |
| State | Zustand + TanStack Query |
| Map | MapLibre GL JS + deck.gl ArcLayer |
| Styling | Tailwind CSS + Headless UI |
| Backend | FastAPI + Uvicorn (Python 3.11+) |
| Database | SQLite + SQLAlchemy 2.0 |
| Scheduler | APScheduler (in-process) |
| Gmail SMTP (smtplib) | |
| Flight APIs | Amadeus SDK + SerpAPI |
- Python 3.11+
- Node.js 20+
cd backend
# Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Configure API keys
cp .env.example .env
# Edit .env and fill in your API keys (see API Keys section below)
# Download airport dataset (one-time, ~5MB)
curl -o app/data/airports.csv https://davidmegginson.github.io/ourairports-data/airports.csv
# Start the backend
uvicorn app.main:app --reload --port 8000Backend is now running at http://localhost:8000 Interactive API docs at http://localhost:8000/docs
cd frontend
# Install dependencies
npm install
# Configure API base URL
cp .env.example .env
# VITE_API_BASE_URL is already set to http://localhost:8000 via Vite proxy
# Start frontend dev server
npm run devFrontend is now running at http://localhost:5173
- Sign up at https://developers.amadeus.com
- Create a new app to get
API_KEYandAPI_SECRET - Free tier: 2,000 flight searches/month
- Use
AMADEUS_HOSTNAME=testfor sandbox (test data),productionfor real pricing
- Sign up at https://serpapi.com
- Copy your API key
- Cost: ~$0.0075 per search (10 searches/day ≈ $2.25/month)
- Enable 2-factor authentication on your Google account
- Go to https://myaccount.google.com/apppasswords
- Create a new app password for "Mail"
- Copy the 16-character password into
GMAIL_APP_PASSWORD
Skyscanner requires a commercial partner agreement and is not available for personal projects. It appears as a disabled option in the UI with an explanatory tooltip.
- Type an airport name or IATA code in the Origin/Destination fields
- Select dates and cabin class
- Choose which data sources to query
- Click Search Flights
Results appear in a sortable list (by price, duration, stops, departure time). Flight routes render as colored arcs on the map:
- Blue arcs: Amadeus results
- Green arcs: Google Flights / SerpAPI results
Hovering a flight card highlights the corresponding arc on the map, and vice versa.
Click "Track price" on any flight result to save that route for daily monitoring. Saved searches appear in the left sidebar.
The backend checks all saved searches daily at 06:00 UTC and records the lowest price from each source.
Click "Send Digest Now" in the sidebar, enter your email address, and the backend will send an HTML table showing:
- All tracked routes with current prices per source
- Price changes since the last check (▼ green = price dropped, ▲ red = price rose)
flight-tracker/
├── backend/
│ ├── app/
│ │ ├── main.py # FastAPI app + APScheduler startup
│ │ ├── config.py # Settings from .env
│ │ ├── database.py # SQLAlchemy + SQLite
│ │ ├── models/ # ORM models (SavedSearch, PriceSnapshot)
│ │ ├── schemas/ # Pydantic models (NormalizedFlight, etc.)
│ │ ├── api/ # REST endpoints (flights, airports, searches, email)
│ │ ├── services/ # Business logic (Amadeus, SerpAPI, aggregator, email)
│ │ └── data/airports.csv # OurAirports dataset (download separately)
│ ├── requirements.txt
│ └── .env.example
│
└── frontend/
├── src/
│ ├── App.tsx # Main layout
│ ├── api/ # TanStack Query hooks
│ ├── store/ # Zustand store (sources, hover state)
│ ├── components/
│ │ ├── search/ # SearchForm, AirportCombobox, SourceToggle
│ │ ├── results/ # ResultsPanel, FlightCard
│ │ ├── map/ # FlightMap (MapLibre + deck.gl)
│ │ ├── saved/ # SavedSearches, price history
│ │ └── email/ # DigestTrigger
│ └── utils/ # formatters, map helpers
├── package.json
└── .env.example
- The SQLite database (
flight_tracker.db) is created automatically in thebackend/directory - Source toggle preferences are persisted to
localStorageand survive page reloads - The Amadeus sandbox returns test data (not real prices); switch to
AMADEUS_HOSTNAME=productionfor live data - Kayak is not supported due to aggressive anti-bot measures and ToS restrictions