-
Notifications
You must be signed in to change notification settings - Fork 140
Expand file tree
/
Copy pathcache-control.middleware.ts
More file actions
124 lines (113 loc) · 3.28 KB
/
Copy pathcache-control.middleware.ts
File metadata and controls
124 lines (113 loc) · 3.28 KB
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// src/middlewares/cache-control.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { PUBLIC_ENDPOINT_CACHE_PRESETS } from '../constants/public-endpoint-cache.constants';
/**
* Cache control options for different types of endpoints.
*/
export interface CacheControlOptions {
/**
* Max age in seconds. Default: 300 (5 minutes)
*/
maxAge?: number;
/**
* Whether the cache is public (CDN can cache) or private (browser only).
* Default: 'public'
*/
type?: 'public' | 'private';
/**
* Whether to include must-revalidate directive.
* Default: false
*/
mustRevalidate?: boolean;
/**
* Whether to include no-cache directive (requires revalidation).
* Default: false
*/
noCache?: boolean;
/**
* Whether to disable caching entirely.
* Default: false
*/
noStore?: boolean;
/**
* Max stale window in seconds. When set, allows serving stale content
* if the origin is unreachable. Default: undefined (disabled)
*/
staleIfError?: number;
}
/**
* Middleware factory that adds Cache-Control headers to responses.
*
* Applies only to GET requests to avoid caching mutations.
* Keeps cache behavior explicit and easy to understand in code.
*
* @param options - Cache control configuration
*
* @example
* // Public endpoint with 5-minute cache
* router.get('/creators', cacheControl({ maxAge: 300 }), listCreators);
*
* @example
* // No caching for sensitive data
* router.get('/profile', cacheControl({ noStore: true }), getProfile);
*/
export function cacheControl(options: CacheControlOptions = {}) {
const {
maxAge = 300,
type = 'public',
mustRevalidate = false,
noCache = false,
noStore = false,
staleIfError,
} = options;
return (req: Request, res: Response, next: NextFunction): void => {
// Only apply cache headers to GET requests
// Mutation routes (POST, PUT, DELETE, PATCH) remain unaffected
if (req.method !== 'GET') {
return next();
}
// Build Cache-Control header value
const directives: string[] = [];
if (noStore) {
directives.push('no-store');
} else if (noCache) {
directives.push('no-cache');
} else {
directives.push(type);
directives.push(`max-age=${maxAge}`);
if (mustRevalidate) {
directives.push('must-revalidate');
}
if (staleIfError !== undefined) {
directives.push(`stale-if-error=${staleIfError}`);
}
}
res.setHeader('Cache-Control', directives.join(', '));
next();
};
}
/**
* Preset cache configurations for common use cases.
*/
export const CachePresets = {
/**
* Short cache for frequently updated public data (5 minutes)
*/
publicShort: PUBLIC_ENDPOINT_CACHE_PRESETS.short,
/**
* Medium cache for moderately stable public data (1 hour)
*/
publicMedium: PUBLIC_ENDPOINT_CACHE_PRESETS.medium,
/**
* Long cache for stable public data (24 hours)
*/
publicLong: PUBLIC_ENDPOINT_CACHE_PRESETS.long,
/**
* Private cache for user-specific data (5 minutes)
*/
private: { maxAge: 300, type: 'private' as const },
/**
* No caching for sensitive or dynamic data
*/
noCache: { noStore: true },
};