Skip to content

HTTP Caching: Caching at the Protocol Level for APIs and Web Pages

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about HTTP Caching: Caching at the Protocol Level for APIs and Web Pages. We cover key concepts, practical examples, and best practices to help you master this topic.

HTTP caching operates at the protocol level, enabling browsers, CDNs, and proxies to cache HTTP responses without application involvement. By leveraging HTTP headers correctly, you can achieve efficient caching for both web pages and API responses without writing application-level cache logic.

flowchart LR
    Browser -->|Request| Proxy[Forward Proxy / CDN]
    Proxy -->|Conditional Request| Origin[Origin Server]
    Origin -->|200 with Cache-Control| Proxy
    Proxy -->|304 with ETag match| Proxy
    Proxy --> Browser
    Browser -->|If-None-Match| Proxy
    Proxy -->|Cache Hit| Browser

What You'll Learn

  • HTTP cache headers for controlling browser and CDN caching
  • Conditional requests (If-None-Match, If-Modified-Since)
  • Caching REST APIs: GET, POST, and error responses
  • Cache invalidation via HTTP methods (PURGE, BAN)

Why It Matters

HTTP caching is the most widely supported caching mechanism. It works across all browsers, CDNs, and proxy servers with zero application changes when configured correctly. Misconfigured HTTP caching is the most common cause of both performance problems and stale-content bugs.

Real-World Use

A SaaS application serves its React SPA with Cache-Control: public, max-age=0, must-revalidate for index.html and Cache-Control: public, max-age=31536000, immutable for JS bundles. API responses use ETags for efficient revalidation. The result: instant page loads after the first visit.

HTTP Caching for APIs

REST API with Conditional Requests

const crypto = require('crypto');

app.get('/api/users/:id', async (req, res) => {
  const user = await db.getUser(req.params.id);
  if (!user) return res.status(404).json({ error: 'Not found' });

  const etag = crypto.createHash('md5').update(JSON.stringify(user)).digest('hex');

  res.set({
    'ETag': etag,
    'Cache-Control': 'private, max-age=0, must-revalidate',
    'Last-Modified': new Date(user.updatedAt).toUTCString()
  });

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();
  }

  res.json(user);
});

Expected output:

First request: 200 with user data and ETag. Second request with If-None-Match: 304 Not Modified, no body.

Caching POST Search Results

app.post('/api/search', async (req, res) => {
  const searchHash = crypto.createHash('md5').update(JSON.stringify(req.body)).digest('hex');
  const cacheKey = `search:${searchHash}`;

  const cached = await cache.get(cacheKey);
  if (cached) {
    res.set('X-Cache', 'HIT');
    return res.json(JSON.parse(cached));
  }

  const results = await searchEngine.query(req.body);

  if (results.length > 0) {
    await cache.setEx(cacheKey, 60, JSON.stringify(results));
  }

  res.set('X-Cache', 'MISS');
  res.json(results);
});

Expected output:

For repeat searches with identical query bodies, results are cached for 60 seconds. X-Cache header indicates hit/miss.

PURGE Method for Cache Invalidation

app.purge('/api/cache/:path(*)', async (req, res) => {
  const path = req.params.path;
  const apiKey = req.headers['x-purge-key'];

  if (apiKey !== process.env.PURGE_KEY) {
    return res.status(403).json({ error: 'Invalid purge key' });
  }

  try {
    // Clear application cache
    await cache.del(`response:${path}`);

    // Send purge request to CDN
    await fetch(`https://cdn.example.com/purge/${path}`, {
      method: 'PURGE',
      headers: { 'X-Purge-Key': process.env.CDN_PURGE_KEY }
    });

    res.json({ purged: path });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

Expected output:

Sending a PURGE request to /api/cache/products invalidates both the application cache and the CDN cache for that path.

Common Mistakes

  • Setting Cache-Control: no-store for all API responses, preventing any caching — use no-cache or short max-age instead.
  • Not including ETags or Last-Modified for API responses, forcing re-download of unchanged data.
  • Caching authenticated responses without Vary: Cookie or Vary: Authorization, leaking user-specific data.
  • Using the same cache policy for 404 and 500 responses — cache 404 briefly to reduce load, but never cache 5xx.
  • Forgetting that HTTP caching is per-URL — query parameters change the cache key unless normalized.

Practice Questions

  1. How does a conditional request reduce bandwidth?
  2. What is the difference between public and private Cache-Control in HTTP caching?
  3. How does the Vary header affect HTTP caching?
  4. When would you use a PURGE request vs. waiting for TTL expiry?
  5. How does HTTP caching differ for API responses vs. static assets?

Challenge

Design the HTTP caching Strategy for a SaaS dashboard. The HTML shell should revalidate always. JS/CSS should cache for 1 year with immutable. User settings API should use private, max-age=0 with ETags. Search API should cache POST results for 60 seconds. Implement all headers and test with curl.

FAQ

What is the difference between HTTP caching and application caching?

HTTP caching uses protocol-level headers (Cache-Control, ETag) and is handled by browsers, CDNs, and proxies. Application caching is explicit cache logic in your code (Redis, in-memory).

How do I invalidate an HTTP cache?

You can: (1) wait for TTL expiry, (2) use PURGE requests (supported by CDNs), (3) change the URL (versioned assets), or (4) use a cache-busting query parameter.

What is a stale-while-revalidate directive?

The stale-while-revalidate Cache-Control extension allows serving stale content while revalidating in the background. This provides instant responses with eventual freshness.

Should I cache API responses?

Yes, but use appropriate policies. Most GET responses can have short max-age (30-300s) with revalidation. Private responses (user data) use private, max-age=0 with ETags.

How do CDNs handle HTTP cache headers?

CDNs respect Cache-Control, ETag, and Expires headers from the origin. They cache responses that are public or have an explicit s-maxage. Origin headers like X-Cache indicate hit/miss.

Mini Project

Build an Express server that serves both a simple HTML page and a REST API. Configure HTTP headers for: index.html (no-cache), bundle.js (1 year, immutable), /api/public (public, max-age=60), /api/private (private, max-age=0, ETag). Use curl to verify all caching behaviors including 304 responses.

What's Next

Complete the caching journey with Cache Project — a comprehensive hands-on project combining all caching strategies.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro