Caching is easy to get wrong. Cache too aggressively and you serve stale data. Cache too little and you get no benefit. Claude Code can implement caching patterns correctly — with the right CLAUDE.md configuration.
CLAUDE.md for Caching
## Caching Rules
### What to Cache
- Master data (user roles, config, feature flags): TTL 5min
- Expensive computations (aggregations, reports): TTL 15min
- User sessions: TTL = session expiry
- API responses from external services: TTL 1min
### What NOT to Cache
- User-specific transactional data (orders, payments in flight)
- Real-time data (stock prices, live notifications)
- Anything that must be consistent immediately after write
### Cache Keys
- Format: `{service}:{entity}:{id}:{version}` or `{service}:{entity}:list:{filter_hash}`
- Examples: `user:profile:123`, `product:list:abc123`, `config:features:v1`
- Never use raw user input in cache keys (injection risk)
### Invalidation Strategy
- Write-through: update cache on every write (for consistency)
- TTL-based: use short TTL when consistency is flexible
- Event-based: invalidate on domain events (prefer this for critical data)
### Redis Configuration
- Client: src/lib/redis.ts (singleton)
- Default serialization: JSON
- Error handling: cache miss on Redis error (never fail the request)
Redis Cache Wrapper
Generate a Redis cache utility module.
Requirements:
- get(key: string): return parsed JSON or null on miss/error
- set(key: string, value: unknown, ttlSeconds: number): set with expiry
- del(key: string | string[]): delete one or multiple keys
- exists(key: string): boolean check
- Never throw on Redis errors — return null/false instead
- Log Redis errors to logger.ts but don't crash
Location: src/lib/cache.ts
Cache-Aside Pattern
Implement the cache-aside pattern for getUserProfile():
1. Check Redis for cached profile
2. On hit: return cached data
3. On miss: fetch from DB, store in Redis (TTL 5min), return
4. On Redis error: fetch from DB without caching (graceful degradation)
Cache key format: user:profile:{userId}
[paste the existing getUserProfile function]
Generated pattern:
async function getUserProfile(userId: string): Promise<UserProfile | null> {
const cacheKey = `user:profile:${userId}`;
// Try cache first
const cached = await cache.get<UserProfile>(cacheKey);
if (cached) return cached;
// Fetch from DB
const profile = await db.user.findUnique({ where: { id: userId } });
if (!profile) return null;
// Store in cache (don't await — non-blocking)
cache.set(cacheKey, profile, 300).catch(logger.error);
return profile;
}
Cache Invalidation
Generate cache invalidation logic for when a user updates their profile.
Requirements:
- Invalidate user:profile:{userId}
- Invalidate any list caches that include this user
- Use a domain event pattern (not direct cache calls in the service)
Pattern: service emits 'user.updated' event → cache handler invalidates
[paste the user update service code]
Read-Through for Lists
Implement paginated user list caching with cursor-based pagination.
Requirements:
- Cache key includes the cursor and limit: user:list:{cursor}:{limit}
- TTL: 2 minutes (shorter due to mutation risk)
- Invalidate all user:list:* keys when any user is created/deleted
- Use Redis SCAN for bulk invalidation (not KEYS — too slow in production)
Hook: Detect Uncached Database Calls
For high-traffic endpoints, flag direct DB calls without caching:
# .claude/hooks/check_cache.py
import json, re, sys
data = json.load(sys.stdin)
content = data.get("tool_input", {}).get("content", "") or ""
fp = data.get("tool_input", {}).get("file_path", "")
# Only check route handlers (not repositories)
if not fp or "routes" not in fp and "controllers" not in fp:
sys.exit(0)
# DB calls without cache check nearby
if re.search(r'prisma\.(user|product|config)\.findMany', content):
if 'cache.get' not in content and 'cache.set' not in content:
print("[CACHE] DB call without caching in route handler", file=sys.stderr)
sys.exit(0)
Testing Cached Functions
Generate tests for the cached getUserProfile function.
Test cases:
- Cache hit: returns cached data without DB call
- Cache miss: fetches from DB, stores in cache, returns data
- DB not found: returns null, nothing stored in cache
- Redis error: fetches from DB as fallback (graceful degradation)
Mock both the Redis client and Prisma client.
Code Review Pack (¥980) on PromptWorks.
Myouga (@myougatheaxo) — Security-focused Claude Code engineer.
Top comments (0)