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

Time-Based Invalidation

The simplest strategy

Time-based invalidation does not track what changed. It says: “After N seconds, this data might be stale. Throw it away and fetch fresh data.” The TTL lesson (Section 3) implemented this. This lesson explains when and how to tune it.

TTL is “good enough” for most data

The previous lesson listed the problems with invalidation: knowing what to invalidate, race conditions, distributed systems. TTL sidesteps all of them:

Knowing what to invalidate: You do not need to know. Every entry expires on its own schedule.

Race conditions: Cannot happen. The cache is never explicitly invalidated — it just expires.

Distributed systems: Each server’s cache expires independently. No coordination needed.

The tradeoff: data can be stale for up to the TTL duration. With a 60-second TTL, a review posted at second 0 is not reflected until second 60. For most applications, this is acceptable.

Choosing TTL by data type

const TTL = {
  // Rarely changes, not time-sensitive
  BOOK_DETAIL: 10 * 60_000, // 10 minutes
  AUTHOR_LIST: 15 * 60_000, // 15 minutes
  CATALOG_STATS: 30 * 60_000, // 30 minutes

  // Changes occasionally, moderate sensitivity
  TOP_BOOKS: 5 * 60_000, // 5 minutes
  BOOK_LIST: 2 * 60_000, // 2 minutes
  GENRE_LIST: 5 * 60_000, // 5 minutes

  // Changes frequently or is time-sensitive
  SEARCH_RESULTS: 30_000, // 30 seconds
  RECENT_REVIEWS: 60_000, // 1 minute

  // External APIs (reduce API calls)
  EXTERNAL_RATING: 24 * 60 * 60_000, // 24 hours
};

The TTL decision framework

Ask three questions:

  1. How often does this data change? If it changes every hour, a 24-hour TTL is too long.
  2. How bad is staleness? If a user sees yesterday’s book count, is that a problem? Probably not. If they see yesterday’s price, maybe.
  3. How expensive is the query? If the query takes 100ms, a short TTL means frequent re-computation. If it takes 1ms, TTL barely matters.
Change frequencyStaleness toleranceQuery costTTL
RarelyHighHigh30-60 minutes
OccasionallyMediumMedium2-10 minutes
FrequentlyLowLow15-60 seconds
Real-timeNoneAnyDo not cache

Different TTLs at different levels

HTTP caching and server-side caching can have different TTLs:

route.get("/books/top", {
  resolve: async () => {
    // Server-side: cache query result for 5 minutes
    const books = await cacheThrough("top-books", 5 * 60_000, () => db.prepare("SELECT ...").all());

    // HTTP: browser caches for 1 minute, stale-while-revalidate for 5 minutes
    return new Response(JSON.stringify(books), {
      headers: {
        "Content-Type": "application/json",
        "Cache-Control": "public, max-age=60, stale-while-revalidate=300",
      },
    });
  },
});

The browser cache (60 seconds) is shorter than the server cache (5 minutes). This means the browser revalidates frequently (every 60 seconds), but most revalidations hit the server cache instead of the database.

Exercises

Exercise 1: Apply the TTL constants to all cached endpoints. Use different TTLs for different data types.

Exercise 2: Set a 10-second TTL. Post a review. Call the endpoint every second. At which second does the new review appear?

Exercise 3: Set different HTTP max-age and server-side TTL values. Trace a request through both cache layers.

Why is TTL-based invalidation sufficient for most applications?

← The Hardest Problem Event-Based Invalidation →

© 2026 hectoday. All rights reserved.