All files / middleware cache-headers.middleware.ts

100% Statements 10/10
100% Branches 6/6
100% Functions 1/1
100% Lines 10/10

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58                      4x                                                         4x 5x     5x 2x     3x   3x 9x 2x 2x        
// Cache-Control header middleware for Cloudflare CDN edge caching.
// Only applies to GET requests with successful responses (status < 400).
// URL patterns are matched in order; first match wins.
 
import type { MiddlewareHandler } from "hono";
 
interface CacheRule {
	pattern: RegExp;
	header: string;
}
 
const CACHE_RULES: CacheRule[] = [
	{
		pattern: /^\/api\/taxonomy\//,
		header: "public, max-age=3600, stale-while-revalidate=7200", // 1hr cache, 2hr stale
	},
	{
		// Shared mood-board reads — short TTL so revocation propagates quickly.
		// Must precede the generic /api/marketplace/ rule (first match wins).
		pattern: /^\/api\/marketplace\/boards\/share\//,
		header: "public, max-age=60",
	},
	{
		pattern: /^\/api\/marketplace\//,
		header: "public, max-age=1800, stale-while-revalidate=3600", // 30min cache, 1hr stale
	},
	{
		pattern: /^\/api\/notifications\//,
		header: "private, no-cache", // User-specific, always revalidate
	},
	{
		pattern: /^\/api\/push\/vapid-public-key/,
		header: "public, max-age=86400", // Static config, cache 24hr
	},
];
 
/**
 * Sets Cache-Control headers on GET responses based on URL pattern matching.
 * Only applies to successful (status < 400) GET requests.
 */
export const cacheHeaders: MiddlewareHandler = async (c, next) => {
	await next();
 
	// Only cache GET requests with successful responses
	if (c.req.method !== "GET" || c.res.status >= 400) {
		return;
	}
 
	const path = c.req.path;
 
	for (const rule of CACHE_RULES) {
		if (rule.pattern.test(path)) {
			c.header("Cache-Control", rule.header);
			return;
		}
	}
};