All files / src/common/middleware cache.middleware.ts

95.83% Statements 23/24
90% Branches 9/10
100% Functions 2/2
95.45% Lines 21/22

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 59 60 611x   1x                       1x 15x       15x 4x       11x 11x   11x           11x 11x   2x         9x 9x     9x   9x   3x 3x 3x         6x 6x      
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { ETagCacheService } from '../cache/etag-cache.service';
 
/**
 * Middleware that checks ETag cache BEFORE handler execution.
 * If client has matching ETag, returns 304 immediately without running handler.
 * This is only for 304 optimization - cache misses still go through handler.
 *
 * To bypass cache from frontend, send either:
 * - Cache-Control: no-cache header
 * - X-Skip-Cache: true header
 */
@Injectable()
export class CacheMiddleware implements NestMiddleware {
  constructor(private readonly cacheService: ETagCacheService) {}
 
  use(req: Request, res: Response, next: NextFunction) {
    // Only cache GET requests
    if (req.method !== 'GET') {
      return next();
    }
 
    // Check for cache bypass headers from frontend
    const cacheControl = req.headers['cache-control'];
    const skipCache = req.headers['x-skip-cache'];
 
    Iif (cacheControl === 'no-cache' || skipCache === 'true') {
      // Frontend explicitly requested fresh data - skip cache
      return next();
    }
 
    // Check If-None-Match header for potential 304
    const clientEtag = req.headers['if-none-match'];
    if (!clientEtag) {
      // No ETag from client - proceed to handler
      return next();
    }
 
    // Generate cache key from URL and query params
    const queryString =
      Object.keys(req.query).length > 0 ? JSON.stringify(req.query) : '';
    const cacheKey = `${req.url}:${queryString}`;
 
    // Check for cached entry
    const cached = this.cacheService.get(cacheKey);
 
    if (cached && clientEtag === cached.etag) {
      // Client has fresh data - send 304 without running handler
      res.setHeader('ETag', cached.etag);
      res.status(304).send();
      return; // Stop - don't call next()
    }
 
    // No cache hit or mismatch - proceed to handler
    // Store cache key for interceptor to use
    res.locals.cacheKey = cacheKey;
    next();
  }
}