hectoday
DocsCoursesChangelog GitHub
DocsCoursesChangelog GitHub

Access Required

Enter your access code to view courses.

Invalid code

← All courses Caching with @hectoday/http

Why Caching

  • The Same Query, A Thousand Times
  • Project Setup

HTTP Caching

  • Cache-Control Headers
  • ETags and Conditional Requests
  • Stale-While-Revalidate

Server-Side Caching

  • In-Memory Caching with Map
  • TTL and Expiration
  • Cache-Aside Pattern
  • LRU Eviction

What to Cache

  • Caching Database Queries
  • Caching Computed Results
  • Caching External API Responses

Invalidation

  • The Hardest Problem
  • Time-Based Invalidation
  • Event-Based Invalidation
  • Tag-Based Invalidation

Putting It All Together

  • Caching Checklist
  • Capstone: Caching the Book Catalog

Caching External API Responses

Why cache external APIs

External API calls are slow (100-500ms network latency), unreliable (the service might be down), and rate-limited (you can only make N calls per minute). Caching reduces all three problems: faster responses, fewer calls, and less risk of hitting rate limits.

The pattern

async function getGoodreadsRating(isbn: string): Promise<number | null> {
  return cacheThrough(`goodreads:${isbn}`, 24 * 60 * 60_000, async () => {
    const response = await fetch(`https://api.goodreads.com/rating?isbn=${isbn}`);
    if (!response.ok) return null;
    const data = await response.json();
    return data.averageRating;
  });
}

The Goodreads rating for a book changes slowly (maybe once a day as new reviews come in). Caching it for 24 hours means one API call per day per book instead of one per request. With 100 requests per day for a popular book, that is 99 fewer API calls.

Stale data as a fallback

When the external API is down, return stale cached data instead of failing:

async function getExternalData(
  key: string,
  fetchFn: () => Promise<any>,
  ttlMs: number,
): Promise<any> {
  const cached = cacheGet<{ value: any; fetchedAt: number }>(key);

  // Fresh cache hit
  if (cached && Date.now() - cached.fetchedAt < ttlMs) {
    return cached.value;
  }

  // Try to fetch fresh data
  try {
    const fresh = await fetchFn();
    cacheSet(key, { value: fresh, fetchedAt: Date.now() }, ttlMs * 10); // Store with extra-long TTL
    return fresh;
  } catch (err) {
    // API is down — return stale cached data if available
    if (cached) {
      console.log(
        `[CACHE] ${key}: API failed, returning stale data (${Math.round((Date.now() - cached.fetchedAt) / 60_000)} min old)`,
      );
      return cached.value;
    }

    // No cache at all — propagate the error
    throw err;
  }
}

The cache stores data with an extra-long TTL (10x the normal TTL). When the API is down, the cache still has the old data and serves it. The user sees slightly stale data instead of an error.

[!NOTE] This is the same cached fallback pattern from the Error Handling course’s Fallbacks and Degradation lesson — now implemented with the caching system from this course. Stale data is better than no data for most read operations.

Rate limit awareness

If the external API has a rate limit (100 calls per hour), cache aggressively to stay under the limit:

const GOODREADS_RATE_LIMIT = 100; // per hour
const CACHE_TTL = Math.ceil(3600 / GOODREADS_RATE_LIMIT) * 1000; // 36 seconds minimum

// With 100 calls/hour limit and 1000 unique books, cache for at least:
// 1000 / 100 = 10 hours to query all books within the rate limit

For APIs with strict rate limits, use longer TTLs and precompute data in background jobs (from the Background Jobs course) instead of caching on first request.

What to cache from external APIs

Cache: Data that changes slowly (ratings, metadata, weather forecasts), reference data (currency exchange rates, country lists), and responses from rate-limited APIs.

Do not cache: Real-time data (stock prices, live scores), authentication tokens from OAuth providers (they expire), and webhook verification responses.

Exercises

Exercise 1: Create a simulated external API (a function with a 500ms delay). Cache its responses. Verify the second call is instant.

Exercise 2: Make the simulated API fail. Verify the stale cached data is returned. Verify the log message indicates stale data.

Exercise 3: Calculate the minimum TTL needed to stay under a rate limit of 60 calls per minute with 200 unique keys.

Why store external API responses with an extra-long TTL beyond the normal freshness window?

← Caching Computed Results The Hardest Problem →

© 2026 hectoday. All rights reserved.