fast-flights-ts
v4.1.0
Published
Fast, strongly-typed Google Flights scraper for TypeScript/Node.js
Maintainers
Readme
fast-flights-ts
Fast, strongly-typed Google Flights scraper for Node.js. Calls Google's internal RPC endpoint directly -- no HTML scraping, no browser required.
npm install fast-flights-tsForked from AWeirdDev/flights (Python). Ported to TypeScript by Claude Opus 4.6.
Quick start
import { createQuery, Passengers, getFlights } from "fast-flights-ts";
const query = createQuery({
flights: [
{ date: "2026-05-25", from_airport: "GIG", to_airport: "SFO" },
],
seat: "economy",
trip: "one-way",
passengers: new Passengers({ adults: 1 }),
currency: "USD",
});
const results = await getFlights(query);
for (const flight of results) {
console.log(
flight.airlines.join(", "),
`$${flight.price}`,
flight.flights.map((f) => `${f.from_airport.code}->${f.to_airport.code}`).join(" "),
);
}Multi-city
Multi-city and open-jaw queries work out of the box:
const query = createQuery({
flights: [
{ date: "2026-05-21", from_airport: "GIG", to_airport: "LAX" },
{ date: "2026-05-28", from_airport: "SFO", to_airport: "GIG" },
],
seat: "business",
trip: "multi-city",
passengers: new Passengers({ adults: 1 }),
});
const results = await getFlights(query);Options
getFlights and fetchFlightsHtml accept a second argument:
const results = await getFlights(query, {
timeout: 15_000, // ms, default 30s
maxRetries: 3, // retry on 429/503/timeout, default 0
retryDelay: 2_000, // base delay with exponential backoff + jitter
signal: controller.signal, // external AbortSignal
debug: true, // log to console
});Integrations
Bright Data
When using an integration, the library falls back to HTML scraping:
import { getFlights, BrightData } from "fast-flights-ts";
const results = await getFlights(query, {
integration: new BrightData({ api_key: "your-key" }),
});Env vars: BRIGHT_DATA_API_KEY, BRIGHT_DATA_API_URL, BRIGHT_DATA_SERP_ZONE.
API
createQuery(options)
| Option | Type | Default |
|--------|------|---------|
| flights | FlightQueryInput[] | required |
| seat | "economy" \| "premium-economy" \| "business" \| "first" | "economy" |
| trip | "round-trip" \| "one-way" \| "multi-city" | "one-way" |
| passengers | Passengers | 1 adult |
| language | Language \| "" | "" |
| currency | Currency \| "" | "" |
| max_stops | number \| null | null |
getFlights(query, options?)
Returns Promise<FlightResults> -- an array of Flights with attached metadata.
Passengers
new Passengers({ adults: 2, children: 1, infants_in_seat: 0, infants_on_lap: 0 })Max 9 total. Infants on lap cannot exceed adult count.
Error handling
All errors extend FlightError:
import { FlightError, HttpError, CaptchaError, TimeoutError, ParseError } from "fast-flights-ts";
try {
await getFlights(query);
} catch (e) {
if (e instanceof TimeoutError) { /* request timed out */ }
if (e instanceof HttpError) { /* e.status, e.responseLength */ }
if (e instanceof CaptchaError) { /* Google wants a CAPTCHA */ }
if (e instanceof ParseError) { /* unexpected response format */ }
}Retry is automatic for 429, 502, 503, 504, and timeouts when maxRetries > 0.
Response types
interface Flights {
type: string;
price: number;
airlines: string[];
flights: SingleFlight[];
carbon: CarbonEmission;
}
interface SingleFlight {
from_airport: Airport; // { code, name }
to_airport: Airport;
departure: SimpleDatetime;
arrival: SimpleDatetime;
duration: number; // minutes
plane_type: string;
}
interface FlightResults extends Array<Flights> {
metadata: JsMetadata; // { airlines, alliances }
}Benchmarks
Compared against the original Python implementation v3 (10,000 iterations, bash bench/cross-lang.sh).
Encoding (query + protobuf + base64)
| | ops/sec | latency | |-|---------|---------| | Python v3 | 214,626 | 4.7 us | | TypeScript | 462,181 | 2.2 us |
2.2x faster.
Parsing (20-flight payload, JSON extract + mapping)
| | ops/sec | latency | |-|---------|---------| | Python v3 | 17,711 | 56.5 us | | TypeScript | 53,911 | 18.5 us |
3.0x faster.
Detailed breakdown (npm run bench)
| Operation | ops/sec | mean |
|-----------|---------|------|
| encodeInfo (simple) | 833,718 | 1.2 us |
| encodeInfo (complex) | 341,613 | 2.9 us |
| encodeInfoToBase64 | 768,435 | 1.3 us |
| createQuery | 16,330,189 | 0.06 us |
| createQuery + toStr() | 728,724 | 1.4 us |
| parseRpcResponse (5 flights) | 151,354 | 6.6 us |
| parseRpcResponse (50 flights) | 17,604 | 56.8 us |
| parseRpcResponse (200 flights) | 4,368 | 229 us |
How it works
- Query -- builds flight parameters (dates, airports, seat, passengers)
- RPC -- POSTs directly to Google's
GetShoppingResultsendpoint - Parse -- strips
)]}'prefix, extracts nested JSON with flight data - Fallback -- HTML scraping via
node-libcurlorfetchwhen using integrations
The RPC approach is used by default for structured queries. It works for all trip types (one-way, round-trip, multi-city) and returns more results than HTML scraping (best + other flights).
Why it's fast
- Direct RPC -- no HTML to download or parse, ~35KB response vs ~2MB HTML
- Manual protobuf encoder (~80 lines, no protobufjs)
- Zero runtime dependencies --
node-libcurlis optional - 20 KB bundled (ESM + CJS dual output)
Development
npm install
npm run typecheck
npm test # 21 tests
npm run bench
npm run buildLicense
MIT -- see LICENSE.
Original Python library by AWeirdDev. RPC approach inspired by swoop.
