Skip to content

Commit 179ff72

Browse files
committed
Add cache middleware to README
1 parent c3cac1c commit 179ff72

File tree

1 file changed

+174
-0
lines changed

1 file changed

+174
-0
lines changed

packages/middleware/README.md

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,180 @@ app.use(
173173
- `formatErrorMessage`: Custom error log format function
174174
- `onLog`: Callback function called after logging
175175

176+
### Cache
177+
178+
High-performance HTTP caching middleware with support for multiple storage backends, conditional requests, and stale-while-revalidate.
179+
180+
```js
181+
import { cache, MemoryCache, LRUCache, RedisCache } from "@rabbit-company/web-middleware";
182+
183+
// Basic in-memory caching
184+
app.use(cache());
185+
186+
// LRU cache with max 500 entries
187+
app.use(
188+
cache({
189+
storage: new LRUCache(500),
190+
ttl: 600, // 10 minutes
191+
})
192+
);
193+
194+
// Redis cache for distributed caching
195+
import Redis from "ioredis";
196+
const redis = new Redis();
197+
198+
app.use(
199+
cache({
200+
storage: new RedisCache(redis),
201+
ttl: 3600, // 1 hour
202+
staleWhileRevalidate: true,
203+
maxStaleAge: 86400, // 24 hours
204+
})
205+
);
206+
207+
// Advanced configuration
208+
app.use(
209+
cache({
210+
storage: new MemoryCache(),
211+
ttl: 300, // 5 minutes default
212+
methods: ["GET", "HEAD"], // Only cache GET/HEAD requests
213+
214+
// Custom cache key generation
215+
keyGenerator: (ctx) => {
216+
const url = new URL(ctx.req.url);
217+
const userId = ctx.get("user")?.id || "anonymous";
218+
return `${ctx.req.method}:${url.pathname}:${userId}`;
219+
},
220+
221+
// Conditional caching
222+
shouldCache: (ctx, res) => {
223+
// Only cache successful responses
224+
if (res.status < 200 || res.status >= 300) return false;
225+
226+
// Don't cache if response is too large
227+
const size = res.headers.get("content-length");
228+
if (size && parseInt(size) > 1024 * 1024) return false; // 1MB limit
229+
230+
return true;
231+
},
232+
233+
// Vary cache by headers
234+
varyHeaders: ["accept", "accept-encoding", "accept-language"],
235+
236+
// Respect Cache-Control headers
237+
respectCacheControl: true,
238+
239+
// Path filtering
240+
excludePaths: ["/api/auth", "/api/admin", /^\/ws/],
241+
includePaths: ["/api/public", "/api/products"],
242+
})
243+
);
244+
245+
// Conditional requests (ETag support)
246+
app.get("/api/data", cache({ ttl: 3600 }), async (ctx) => {
247+
const data = await getExpensiveData();
248+
return ctx.json(data);
249+
// Middleware automatically generates ETag and handles If-None-Match
250+
});
251+
252+
// Stale-while-revalidate pattern
253+
app.use(
254+
cache({
255+
ttl: 60, // Fresh for 1 minute
256+
staleWhileRevalidate: true,
257+
maxStaleAge: 3600, // Serve stale for up to 1 hour while revalidating
258+
})
259+
);
260+
261+
// Cache invalidation
262+
import { cacheUtils } from "@rabbit-company/web-middleware";
263+
264+
// Invalidate specific cache keys
265+
app.post(
266+
"/api/posts",
267+
createPost,
268+
cacheUtils.invalidate({
269+
storage: cache.storage,
270+
keys: ["GET:/api/posts", "GET:/api/posts/latest"],
271+
})
272+
);
273+
274+
// Pattern-based invalidation (Redis only)
275+
app.put(
276+
"/api/posts/:id",
277+
updatePost,
278+
cacheUtils.invalidate({
279+
storage: redisCache,
280+
patterns: ["GET:/api/posts/*", "GET:/api/users/*/posts"],
281+
})
282+
);
283+
284+
// Clear entire cache
285+
await cacheUtils.clear(cache.storage);
286+
287+
// Get cache statistics
288+
const stats = await cacheUtils.stats(cache.storage);
289+
console.log(`Cache size: ${stats.size} entries`);
290+
291+
// Custom storage implementation
292+
class CustomStorage {
293+
async get(key) {
294+
/* ... */
295+
}
296+
async set(key, entry, ttl, maxStaleAge) {
297+
/* ... */
298+
}
299+
async delete(key) {
300+
/* ... */
301+
}
302+
async clear() {
303+
/* ... */
304+
}
305+
async has(key) {
306+
/* ... */
307+
}
308+
async size() {
309+
/* ... */
310+
}
311+
}
312+
313+
app.use(cache({ storage: new CustomStorage() }));
314+
```
315+
316+
#### Storage Backends:
317+
318+
- **MemoryCache**: In-memory storage with automatic expiry
319+
- **LRUCache**: Least Recently Used eviction when size limit reached
320+
- **RedisCache**: Distributed caching with Redis
321+
322+
#### Options:
323+
324+
- `storage`: Cache storage backend (default: MemoryCache)
325+
- `ttl`: Time to live in seconds (default: 300)
326+
- `methods`: HTTP methods to cache (default: ["GET", "HEAD"])
327+
- `keyGenerator`: Custom cache key generation function
328+
- `shouldCache`: Function to determine if response should be cached
329+
- `varyHeaders`: Headers to include in cache key (default: ["accept", "accept-encoding"])
330+
- `respectCacheControl`: Honor Cache-Control headers (default: true)
331+
- `addCacheHeader`: Add X-Cache-Status header (default: true)
332+
- `cacheHeaderName`: Custom cache status header name (default: "x-cache-status")
333+
- `cachePrivate`: Cache private responses (default: false)
334+
- `excludePaths`: Paths to exclude from caching
335+
- `includePaths`: Only cache these paths (if set)
336+
- `staleWhileRevalidate`: Serve stale content while revalidating (default: false)
337+
- `maxStaleAge`: Maximum stale age in seconds (default: 86400)
338+
339+
#### Features:
340+
341+
- Automatic ETag generation and conditional request handling
342+
- Stale-while-revalidate for better performance
343+
- Cache-Control header support
344+
- Vary header support for content negotiation
345+
- Pattern-based invalidation with Redis
346+
- Background revalidation for stale content
347+
- Distributed caching with Redis backend
348+
- Memory efficient with configurable storage limits
349+
176350
### Bearer Auth
177351
178352
Token-based authentication for APIs.

0 commit comments

Comments
 (0)