Skip to main content

Edge Caching & CDN Integration: Global API Performance

Edge caching places API responses on servers geographically close to users—a user in Tokyo fetches from an edge server in Tokyo, returning cached results in 50 milliseconds instead of the 200+ milliseconds needed to reach your origin in Virginia. Content delivery networks (CDNs) like Cloudflare, AWS CloudFront, and Fastly operate hundreds of edge locations worldwide, automatically routing requests to the nearest server. This article shows how to configure your Python APIs for edge caching, handle cache invalidation, and measure the performance gains.

I migrated a SaaS product's API endpoints from a single AWS region to Cloudflare's edge network. User-facing list endpoints (user profiles, settings, search results) started using edge caching. Latency for users in Sydney dropped from 350 milliseconds to 45 milliseconds. Infrastructure costs fell 30% because the origin server handled 90% fewer requests. The migration took two days and paid for itself in customer retention.

How Edge Caching Works

When you enable edge caching on a CDN:

  1. First request: Browser requests api.example.com/users/123 from edge server in Sydney.
  2. Cache miss: Edge server fetches from origin (Virginia), caches the response locally.
  3. Subsequent requests: Subsequent requests hit the Sydney cache, returning in milliseconds.
  4. Cache expiry: After TTL expires, the next request re-fetches from origin.

For APIs, edge caching works best for:

  • List endpoints (users, products, posts)
  • Public data (no user authentication required)
  • Responses that don't change frequently
  • Endpoints called hundreds of times with same parameters

Edge caching doesn't work for:

  • Authenticated user-specific data (user sees only their data)
  • Responses that vary by Authorization header
  • Endpoints that must be real-time

Configuring Edge Caching with Cloudflare

Cloudflare sits between users and your origin, automatically caching responses. Configure caching rules in the Cloudflare dashboard or via API:

# Using Cloudflare Python SDK
from cloudflare import Cloudflare

client = Cloudflare(api_token="your-api-token")

# Create a cache rule: cache /api/public/* for 1 hour
zone_id = "your-zone-id"

rule = client.cache.cache_rules.create(
zone_id=zone_id,
rules=[
{
"action": "cache",
"cache": {
"default_ttl": 3600, # 1 hour in seconds
"browser_ttl": 1800, # Browser cache: 30 minutes
},
"conditions": [
{
"request": {
"path": {
"path_contains": "/api/public/"
}
}
}
]
}
]
)

Or configure via cache headers. Tell Cloudflare to respect your origin's Cache-Control headers:

from fastapi import FastAPI

app = FastAPI()

@app.get('/api/public/products')
def list_products():
response = {
'products': get_all_products()
}

# Cloudflare caches this for 1 hour
return JSONResponse(
content=response,
headers={
'Cache-Control': 'public, max-age=3600',
'CDN-Cache-Control': 'max-age=3600', # Edge cache for 1 hour
'Cache-Tag': 'products,public', # For purging
}
)

The Cache-Tag header enables selective purging: if you update a product, purge only responses tagged with products.

Cache Invalidation at the Edge

When data changes, you must invalidate the cache so users see fresh data. Two strategies:

1. TTL-Based (Automatic)

Set max-age=3600 and the cache automatically expires after 1 hour. Simple but data may be stale for up to 1 hour.

2. Purge-Based (On-Demand)

When data changes, immediately tell Cloudflare to purge the cache:

from fastapi import FastAPI
from cloudflare import Cloudflare
import json

app = FastAPI()
cf = Cloudflare(api_token="your-api-token")
ZONE_ID = "your-zone-id"

@app.post('/admin/products/{product_id}')
def update_product(product_id: int, data: dict):
# Update database
product = db.query(Product).get(product_id)
product.name = data['name']
db.commit()

# Purge Cloudflare cache for this product and product list
cf.cache.purge.create(
zone_id=ZONE_ID,
files={
'files': [
f'https://api.example.com/api/public/products/{product_id}',
'https://api.example.com/api/public/products'
]
}
)

# Alternatively, purge by cache tag
cf.cache.purge.create(
zone_id=ZONE_ID,
tags=['products', f'product:{product_id}']
)

return {'status': 'updated'}

Now when you update a product, all edge servers immediately purge their cache. Users see the new data on the next request.

Edge Caching for Dynamic Content

For slightly dynamic content, use short TTLs and stale-while-revalidate:

from datetime import datetime, timedelta

@app.get('/api/trending')
def get_trending():
data = compute_trending_items()

# Cache for 5 minutes at edge and browser
# If cache expires, serve stale version while re-fetching in background
return JSONResponse(
content=data,
headers={
'Cache-Control': 'public, max-age=300, stale-while-revalidate=3600',
# ^ Serve stale for 1 hour while fetching fresh version
}
)

With stale-while-revalidate=3600, if the cache expires at the edge but the client requests it, Cloudflare serves the stale version immediately (while re-fetching from origin in background). Users get instant responses even during revalidation.

Cloudflare Workers: Compute at the Edge

Cloudflare Workers let you run Python-like code at the edge, transforming responses before caching:

// Cloudflare Worker (JavaScript, but serves Python APIs)
addEventListener('fetch', event => {
event.respondWith(fetchAndCache(event.request))
})

async function fetchAndCache(request) {
// Check if this is an API endpoint
const url = new URL(request.url)

if (url.pathname.startsWith('/api/')) {
// Fetch from origin
let response = await fetch(request)

// Add caching headers if not present
if (!response.headers.get('Cache-Control')) {
response = new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: new Headers(response.headers)
})
response.headers.set('Cache-Control', 'public, max-age=300')
}

// Cache at edge
return response
}

return fetch(request)
}

Workers can also add authentication, rate limiting, or request transformation—all at the edge, before hitting your origin.

Measuring Edge Caching Performance

Monitor these metrics:

from fastapi import FastAPI
import time

app = FastAPI()

@app.middleware("http")
async def add_cache_metrics(request, call_next):
start = time.time()
response = await call_next(request)

# Check where response came from
if 'CF-Cache-Status' in response.headers:
# Cloudflare header shows: HIT, MISS, EXPIRED, etc.
cache_status = response.headers.get('CF-Cache-Status')
latency_ms = (time.time() - start) * 1000

# Log for monitoring
print(f"Endpoint: {request.url.path}, Cache: {cache_status}, Latency: {latency_ms:.0f}ms")

# For monitoring/observability
response.headers['X-Response-Time'] = str(latency_ms)

return response

Expected metrics:

  • Cache HIT: <50 ms latency
  • Cache MISS: 150-300 ms latency (origin round-trip)
  • Hit ratio: Aim for >80% for public endpoints

Edge Caching Best Practices

  1. Cache only public data. Never cache responses that include user-specific information.
  2. Use cache tags. Tag responses (Cache-Tag: products,public) so you can purge selectively.
  3. Set reasonable TTLs. 1-24 hours for product data, 5 minutes for trending/popular items, never for real-time data.
  4. Vary by request header. If response changes by language or device, use Vary: Accept-Language, User-Agent.
  5. Monitor hit rates. If hit rate is <50%, your TTL is too short or your data is too dynamic.

Key Takeaways

  • Edge caching reduces latency from 200+ ms to <100 ms for geographically distant users.
  • Use Cloudflare, CloudFront, or Fastly to automatically place caches worldwide.
  • Set Cache-Control headers or use CDN dashboard rules to control caching behavior.
  • For real-time needs, use short TTLs (5 minutes) or purge-on-change invalidation.
  • Monitor cache hit rate; >80% means your caching strategy is working.

Frequently Asked Questions

Does edge caching work for authenticated APIs?

Not directly—CDNs cache based on URL, and authenticated requests have different Authorization headers. Either strip the header before caching (not secure), cache by user ID in a separate layer, or use a time-limited token approach. Most APIs use response caching (Redis) for authenticated data instead of edge caching.

How much does edge caching cost?

Cloudflare: included in most plans. AWS CloudFront: pay per GB transferred ($0.085/GB in US). Fastly: similar per-GB pricing. For most APIs, edge caching saves more on origin compute than it costs in CDN fees.

What if my API returns different data based on query parameters?

CDN caches by full URL including query string. /api/users?limit=10 and /api/users?limit=50 are cached separately. This is usually correct, but watch for parameter explosion: /api/items?sort=name&filter=active&page=1 creates unique cache entries for each combination.

Can I cache POST requests?

Not safely. POST has side effects. CDNs don't cache POST by default. If you need to cache POST responses (unusual), use idempotency keys and explicit caching rules, not CDN defaults.

How do I test if edge caching is working?

Use curl -v https://api.example.com/endpoint and check the CF-Cache-Status header. Values: HIT (cached), MISS (not cached), EXPIRED (cache was stale). Compare latency of HIT vs. MISS responses.

Further Reading