Serverless Flight Data Proxy — Cloudflare Workers, Vercel Edge and AWS Lambda Patterns

How to build a serverless proxy for a flight data API. Practical patterns for Cloudflare Workers, Vercel Edge Functions and AWS Lambda — API key safety, response caching, rate limiting and cost — with complete code examples using AirLabs.

Author
Sergey St.
Share:

Why the Edge Fits Flight Data

If you are building a mobile app, a single-page application on any frontend framework, or a lightweight web tool that consumes flight data, you almost certainly have the same problem: the API key cannot ship to the client. Every serious flight data API is paid at the point of production traffic, and every key that ends up in a mobile bundle, a JavaScript file or a browser DevTools tab is a key that will be scraped and used against you within hours. Full backends — Django, Rails, Node with Express — solve the problem but bring an operational overhead that is disproportionate to the actual work: for many applications, the backend does nothing except forward flight queries and hide the key.

Serverless functions at the edge are the natural fit for this shape of problem. A single small function, deployed globally, receives client requests, adds the API key on the server side, forwards the request to AirLabs, caches the response and returns it. There is no server to run, no framework to configure, no build process beyond deploying the function. Cloudflare Workers, Vercel Edge Functions, AWS Lambda + API Gateway and Supabase Edge Functions all support this pattern with minor syntactic differences and identical architecture.

This guide walks through the same proxy built on each of the three most common platforms — Cloudflare Workers, Vercel Edge Functions, AWS Lambda — plus the caching, rate limiting and cost concerns that determine whether the pattern scales in production. Companion guides in this series cover the Python Production Patterns integration used in longer-lived backend services and the Next.js Server Components approach used when the entire application is on Vercel. Serverless is what you reach for when the client is on a different framework, when you need a global cache in front of the API, or when the operational profile of a full backend is more than the workload justifies.

"An edge proxy is a hundred lines of code that fixes an entire category of security bugs. The API key stops being a secret you protect by discipline and becomes a secret the platform makes impossible to leak. That is the trade — small function, global deployment, and the client never touches the credential."

The API Proxy Pattern

Every serverless proxy for a flight data API implements the same shape:

  • Receive a request from the client — a browser, a mobile app or another service
  • Extract the parameters the client is asking about — a flight code, an airport, a time window
  • Construct the upstream AirLabs request with the API key injected server-side
  • Forward the request; wait for the response
  • Optionally transform the response — strip fields, reshape data, normalise types
  • Return the response to the client with appropriate cache and CORS headers

Every step is small. The interesting design decisions are around what to cache, what to rate-limit, and what to expose. The proxy should be permissive enough that legitimate clients get fast responses, and defensive enough that a leaked proxy URL cannot be used to exhaust the AirLabs plan quota through arbitrary traffic.

The examples below implement the same proxy endpoint — a GET /api/flight/:iata route that returns the status of a specific flight — on three platforms. The pattern extends directly to any AirLabs endpoint: schedules, delays, real-time flights, reference databases.

Cloudflare Workers Example

Cloudflare Workers run on Cloudflare's global edge network, colocated with the caches Cloudflare already operates. For a flight data proxy the pairing is natural: the same platform handles the request routing, the function execution and the response cache.

A minimal working proxy for a single flight lookup:

// worker.js
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    const flightIata = url.pathname.split('/').pop();

    if (!flightIata) {
      return new Response('Missing flight code', { status: 400 });
    }

    // Check the edge cache first
    const cache = caches.default;
    const cacheKey = new Request(url.toString(), request);
    let response = await cache.match(cacheKey);
    if (response) return response;

    // Forward to AirLabs with the key on the server side
    const airlabsUrl = new URL('https://airlabs.co/api/v9/flight');
    airlabsUrl.searchParams.set('flight_iata', flightIata);
    airlabsUrl.searchParams.set('api_key', env.AIRLABS_API_KEY);

    const upstream = await fetch(airlabsUrl.toString());
    const body = await upstream.json();

    // Return only the payload; discard error metadata that could hint at the upstream
    const payload = body.response ?? {};
    response = new Response(JSON.stringify(payload), {
      headers: {
        'content-type': 'application/json',
        'cache-control': 'public, max-age=30',
        'access-control-allow-origin': '*',
      },
    });

    // Populate the edge cache for subsequent requests
    ctx.waitUntil(cache.put(cacheKey, response.clone()));
    return response;
  },
};

Deployment is one command through Wrangler, Cloudflare's CLI:

npm install -g wrangler
wrangler init flight-proxy
# Set the secret
wrangler secret put AIRLABS_API_KEY
wrangler deploy

The resulting URL, something like https://flight-proxy.you.workers.dev/api/flight/BA117, is what your mobile app or SPA calls. The AIRLABS_API_KEY is stored encrypted in Cloudflare and injected at execution time; the client sees only the flight payload.

Cloudflare's edge cache, invoked through caches.default, operates per data center — each of Cloudflare's colocations maintains its own cache, so a response cached in Frankfurt does not automatically appear in Los Angeles. In practice this still absorbs the majority of traffic per region because clients in a region tend to hit the same colo. For a globally shared cache — where a single response is reused across all regions — Cloudflare KV or Cache Reserve are the appropriate primitives, at additional cost. For most flight data workloads, per-colo caching is sufficient.

Vercel Edge Functions Example

Vercel Edge Functions expose the same execution model — small JavaScript function, global deployment, environment variables injected at runtime — through a slightly different API. If your product is already deployed on Vercel or if you prefer the Next.js hosting ecosystem, this is the natural choice.

Create api/flight/[iata].js:

export const config = {
  runtime: 'edge',
};

export default async function handler(request) {
  const url = new URL(request.url);
  const iata = url.pathname.split('/').pop();

  if (!iata) {
    return new Response('Missing flight code', { status: 400 });
  }

  const airlabsUrl = new URL('https://airlabs.co/api/v9/flight');
  airlabsUrl.searchParams.set('flight_iata', iata);
  airlabsUrl.searchParams.set('api_key', process.env.AIRLABS_API_KEY);

  const upstream = await fetch(airlabsUrl.toString());
  const body = await upstream.json();
  const payload = body.response ?? {};

  return new Response(JSON.stringify(payload), {
    headers: {
      'content-type': 'application/json',
      'cache-control': 'public, max-age=30, s-maxage=30',
      'access-control-allow-origin': '*',
    },
  });
}

Deploy through vercel deploy. Set the API key with vercel env add AIRLABS_API_KEY. The endpoint at https://yourproject.vercel.app/api/flight/BA117 is now a working proxy.

Vercel's edge network reads the response Cache-Control header and caches accordingly — s-maxage=30 tells the edge to cache for 30 seconds regardless of browser cache behaviour. Inside a Next.js application, the same result is achievable through next: { revalidate: 30 } on the upstream fetch, but for standalone edge functions the Cache-Control header is the portable, explicit mechanism.

AWS Lambda + API Gateway Example

For teams operating on AWS or with existing IAM, logging and monitoring in that ecosystem, Lambda + API Gateway supports the same proxy pattern. The function itself is a plain Node.js handler:

// flight-proxy.mjs
export const handler = async (event) => {
  const flightIata = event.pathParameters?.iata;

  if (!flightIata) {
    return {
      statusCode: 400,
      headers: { 'content-type': 'text/plain' },
      body: 'Missing flight code',
    };
  }

  const url = new URL('https://airlabs.co/api/v9/flight');
  url.searchParams.set('flight_iata', flightIata);
  url.searchParams.set('api_key', process.env.AIRLABS_API_KEY);

  const upstream = await fetch(url.toString());
  const body = await upstream.json();
  const payload = body.response ?? {};

  return {
    statusCode: 200,
    headers: {
      'content-type': 'application/json',
      'cache-control': 'public, max-age=30',
      'access-control-allow-origin': '*',
    },
    body: JSON.stringify(payload),
  };
};

The AIRLABS_API_KEY is set through the Lambda console or through Infrastructure as Code (SAM, CDK, Terraform). API Gateway routes GET /flight/{iata} to this Lambda.

For response caching, AWS's own CloudFront distribution can sit in front of API Gateway with the cache TTL set through the origin's Cache-Control header — the same convention the Cloudflare and Vercel examples use.

Signature-Based Authentication — The Zero-Proxy Alternative

The proxy patterns above route every client request through a serverless function that forwards to AirLabs. There is an official alternative that removes the forwarding step entirely: AirLabs supports temporary signature-based authentication that lets the client call AirLabs directly, without ever seeing the API key.

The mechanism from the AirLabs documentation works like this. Instead of sending an api_key parameter, the client sends a signature parameter constructed on your backend:

signature = api_id : timestamp : md5(timestamp : api_key)
  • api_id — a public identifier available in response.key.id on any authenticated response
  • timestamp — the current Unix timestamp in seconds
  • md5(...) — MD5 hash of the timestamp concatenated with the API key

The signature is valid for three minutes. Your backend generates it and returns it to the client; the client uses it directly against AirLabs; the API key never leaves your server, but neither does client traffic flow through your infrastructure.

A minimal signature generator running on a serverless function:

// signature-endpoint.js — runs anywhere (Cloudflare, Vercel, Lambda)
import { createHash } from 'crypto';

export default async function handler(request, env) {
  const apiKey = env.AIRLABS_API_KEY;
  const apiId = env.AIRLABS_API_ID;

  const timestamp = Math.floor(Date.now() / 1000);
  const hash = createHash('md5')
    .update(`${timestamp}:${apiKey}`)
    .digest('hex');

  return new Response(
    JSON.stringify({
      signature: `${apiId}:${timestamp}:${hash}`,
      expires_at: timestamp + 180,
    }),
    {
      headers: {
        'content-type': 'application/json',
        'cache-control': 'no-store',
        'access-control-allow-origin': '*',
      },
    },
  );
}

The client fetches a fresh signature every three minutes and uses it against AirLabs directly:

// Client code (browser or mobile app)
let currentSignature = null;
let signatureExpiresAt = 0;

async function getSignature() {
  const now = Math.floor(Date.now() / 1000);
  if (currentSignature && signatureExpiresAt > now + 10) {
    return currentSignature;
  }
  const res = await fetch('https://yourdomain.com/api/signature');
  const data = await res.json();
  currentSignature = data.signature;
  signatureExpiresAt = data.expires_at;
  return currentSignature;
}

async function getFlight(flightIata) {
  const signature = await getSignature();
  const url = `https://airlabs.co/api/v9/flight?flight_iata=${flightIata}&signature=${signature}`;
  const res = await fetch(url);
  return (await res.json()).response;
}

When to prefer signature over a full proxy:

  • Latency-sensitive applications where the extra proxy hop adds noticeable delay
  • Cost-sensitive deployments where minimising function invocations matters
  • Applications that want to leverage AirLabs' native error handling and rate limiting without a custom layer

When a full proxy is still the right answer:

  • Response caching at the edge is a primary design goal — signature-based access bypasses your cache entirely
  • Response transformation, field filtering or aggregation happens on your side
  • Rate limiting per client identity is required — with signature-based access, all clients hit AirLabs directly
  • Enterprise auditing or logging of every flight query is required — proxying is how you observe

Many production deployments end up using both patterns: a signature endpoint for high-frequency real-time queries where latency matters, and a proxy for cached reference data (airports, airlines, fleets) where the cache absorbs most of the load.

Response Caching on the Edge

Every serverless flight proxy needs a caching story. Uncached, every client request becomes an AirLabs request; the proxy adds latency and cost without reducing load. Cached correctly, the proxy absorbs the vast majority of traffic at the edge and only the cache-miss rate flows through to the upstream API.

Three caching layers commonly compose:

  • CDN/edge cache — the platform's built-in HTTP cache, keyed by URL and populated by Cache-Control headers. This is what cache-control: public, max-age=30 in the examples above activates. Every subsequent identical request within 30 seconds is served from the edge cache without invoking the function again.
  • KV store — a lightweight key-value store for structured cache entries. Cloudflare KV, Vercel KV, AWS DynamoDB or Upstash Redis all fit here. Useful when the cache key is not just the URL — for example, when different API tokens should see different cached responses.
  • In-function memory cache — very short-lived, per-instance cache using JavaScript's Map. Useful for high-cardinality endpoints where even a few seconds of caching per instance reduces upstream calls.

For flight data specifically, the caching TTL depends on the endpoint:

  • Flight status by identifier — 30–60 seconds. Status can change within a minute during boarding or push-back.
  • Airport schedules — 30 seconds. Board updates are the primary use case; longer TTLs feel stale.
  • Reference databases (airports, airlines, fleets) — 1–24 hours. This data changes slowly.
  • Real-time flight positions — 15–30 seconds. Aircraft positions update rapidly but 15-second staleness is acceptable for maps.

Aggressive caching of reference data is the single largest reduction in effective API request rate you can make. An airports?iata_code=LHR request cached for an hour serves potentially thousands of client lookups on one AirLabs call.

Rate Limiting at the Edge

The other production concern is rate limiting inbound to your proxy. Without it, a leaked proxy URL — or a client with a runaway bug — can send unbounded traffic that exhausts your AirLabs plan quota. AirLabs enforces three levels of quota which surface as distinct error codes: minute_limit_exceeded, hour_limit_exceeded and month_limit_exceeded. Your proxy should defend against all three by rate-limiting inbound requests before they reach the upstream call.

A minimal per-IP rate limiter in Cloudflare Workers using KV:

async function checkRateLimit(env, clientIp) {
  const key = `rl:${clientIp}`;
  const currentRaw = await env.RATE_LIMIT_KV.get(key);
  const count = currentRaw ? parseInt(currentRaw, 10) : 0;

  const LIMIT_PER_MINUTE = 60;
  if (count >= LIMIT_PER_MINUTE) return false;

  await env.RATE_LIMIT_KV.put(key, String(count + 1), {
    expirationTtl: 60,
  });
  return true;
}

// In the fetch handler:
const clientIp = request.headers.get('CF-Connecting-IP') || 'unknown';
const allowed = await checkRateLimit(env, clientIp);
if (!allowed) {
  return new Response('Rate limit exceeded', { status: 429 });
}

For Vercel, the equivalent uses Vercel KV; for AWS, DynamoDB with TTL. The pattern is the same — increment a counter keyed by client identity, expire after the window, reject when the counter exceeds the limit.

For an authenticated proxy, rate-limit by client token rather than by IP. Mobile apps behind carrier NAT and shared corporate networks can all originate from the same IP, and blocking by IP unfairly locks out entire cohorts.

CORS and Client Integration

Frontend applications served from a different origin than the proxy need CORS headers. Every example above sets access-control-allow-origin: * to allow any browser origin; production deployments typically restrict this to specific origins your application uses.

For a browser-based single-page app:

// In the client
const response = await fetch('https://flight-proxy.yourdomain.com/api/flight/BA117');
const flight = await response.json();
console.log(flight.status, flight.dep_iata, flight.arr_iata);

For a native mobile app:

// React Native / Flutter / iOS / Android
const response = await fetch('https://flight-proxy.yourdomain.com/api/flight/BA117');
const flight = await response.json();

The pattern is identical because the proxy exposes a plain REST endpoint. The proxy URL is the only credential your client needs, and it references no secret material.

Cost Considerations

For a small-to-medium flight data application, the edge functions themselves are inexpensive to the point of being effectively free on the generous startup tiers:

  • Cloudflare Workers — 100,000 requests per day on the free tier; $5/month for 10 million requests
  • Vercel Edge Functions — 500,000 executions per month on the Hobby tier; scales up with usage
  • AWS Lambda — 1 million free requests per month on the always-free tier; API Gateway adds request-based costs

The cost that matters is the AirLabs plan cost, which scales with your cache miss rate rather than with client requests. A well-cached proxy serving one million client requests to a service where the cache hit rate is 95% only makes 50,000 upstream AirLabs calls. Sizing the AirLabs plan against the cache miss rate — not the raw client traffic — is what makes the pattern work economically at scale.

What Serverless Doesn't Solve

Being clear about scope avoids surprises. Serverless proxies solve API key safety, response caching and edge distribution — they do not solve every concern of a production flight data application:

  • Alert API webhook subscriptions — the Flight Alert API sends webhooks to your endpoint on flight-level field changes. Serverless functions can receive them, but the listener subscription state (listener_id, active flights, associated users) must live in a persistent database. Serverless functions are stateless by design.
  • Long-lived connections — WebSockets or Server-Sent Events for streaming updates to clients typically require a persistent connection layer that pure serverless does not natively provide. Services like Cloudflare Durable Objects, AWS API Gateway WebSocket routes or dedicated real-time platforms fill this gap.
  • Aggregation across multiple upstreams — for services that combine AirLabs data with other providers, a proxy that fans out to multiple APIs and aggregates results still works serverlessly but the reliability model gets more complex (partial failures, timeouts).
  • Complex authentication — session management, OAuth flows and enterprise SSO can be layered on serverless but often benefit from a full backend framework.

Practical Notes for Production

  • Always inject the API key on the server side, never expose it to the client. This is the entire point of the proxy pattern. Store the key in the platform's secret manager (Wrangler secrets, Vercel environment variables, AWS Secrets Manager or Parameter Store).
  • Set Cache-Control headers on every response. Even a 15-second cache reduces upstream load meaningfully at scale.
  • Return only the payload, not the entire AirLabs response envelope. Clients do not need the request metadata, and stripping the envelope reduces payload size.
  • Use _fields on the upstream request. The AirLabs _fields parameter reduces the size of the upstream response, which reduces both bandwidth and cache size.
  • Rate-limit by client identity, not by IP alone. Shared IPs behind carrier NAT will block legitimate users if IP-only rate limits are aggressive.
  • Log rate-limit rejections separately from other errors. Rate-limit hits are a healthy signal (abuse rejected); mixing them with actual errors makes on-call harder.
  • Set an explicit timeout on the upstream fetch. Ten seconds is a reasonable default. Without it, a slow AirLabs response holds a serverless function open longer than necessary and can trigger the platform's own timeouts.
  • Deploy the proxy in multiple regions if latency matters. All three platforms deploy globally by default; verify that the cold-start behaviour meets your latency budget for the first request in a region.
  • Monitor the cache hit rate. It is the single number that predicts your AirLabs bill. A hit rate below 80% means the caching is not doing its job; a hit rate above 99% means you might be able to reduce your AirLabs plan.

Flight Data on the Edge for Modern Frontends

If you are shipping a mobile app, a Vue or Svelte SPA, an internal dashboard on any framework, or any client that cannot safely hold a paid API key, a serverless proxy in front of the AirLabs API is the shortest path from a working prototype to a production-grade deployment. The pattern is the same across Cloudflare Workers, Vercel Edge Functions and AWS Lambda: a small function, the API key on the server side, cached responses at the edge, rate limiting to protect the quota. Deployed globally, it turns AirLabs into an infinitely-scalable data source your client applications can call without ever holding a secret.

Supported API Features

Our Developer API allows you to create a custom experience for your users and increase the value of your product:

  • Real-Time Flights API for live aircraft positions with tail number, ICAO hex, altitude, speed and heading
  • Flight Information API for detailed status per flight — scheduled, estimated, delayed, gate, terminal, aircraft
  • Schedules API for departures and arrivals at any airport, with codeshare fields
  • Flight Delays API for currently delayed flights at a specific airport
  • Flight Alert API for webhook-based notifications on flight-level field changes
  • NearBy API for airports within a geographic radius
  • Name Suggestion API for autocomplete of airport, city and country names
  • Reference databases for Airports, Airlines, Cities, Fleets, Routes, Countries and Timezones
  • Field selection via _fields for lean, targeted responses
  • JSON, XML and CSV response formats behind a single API key

You can try it right now without any obligation! Get a free flight API plan and see for yourself that we have exactly the data you need!

If you need more information, don't hesitate to contact us. We are always happy to chat with our customers and are sure to find a customized solution for each request.

Ready to get started?

Explore AirLabs, or create an account instantly and start using API.

Get FREE API Key