Caching and Timeout

A Timeout Interceptor

src/common/interceptors/timeout.interceptor.ts

import { CallHandler, ExecutionContext, Injectable, NestInterceptor, RequestTimeoutException } from '@nestjs/common';
import { Observable, TimeoutError, catchError, throwError, timeout } from 'rxjs';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(_context: ExecutionContext, next: CallHandler): Observable<unknown> {
    return next.handle().pipe(
      timeout(5000),
      catchError((err) =>
        throwError(() => (err instanceof TimeoutError ? new RequestTimeoutException('Took too long') : err)),
      ),
    );
  }
}

Example

The same race between the handler and a timer, using promises. The slow call loses to a 100 ms limit:

class RequestTimeoutException extends Error {}

function withTimeout<T>(work: Promise<T>, ms: number): Promise<T> {
  let timer: NodeJS.Timeout | undefined;
  const limit = new Promise<never>((_, reject) => {
    timer = setTimeout(() => reject(new RequestTimeoutException('408 Took too long')), ms);
  });
  return Promise.race([work, limit]).finally(() => clearTimeout(timer));
}

const delay = <T>(ms: number, value: T) => new Promise<T>((resolve) => setTimeout(() => resolve(value), ms));

async function main() {
  console.log(await withTimeout(delay(10, 'fast product list'), 100));
  try {
    await withTimeout(delay(300, 'slow report'), 100);
  } catch (e) {
    console.log((e as Error).message);
  }
}
main();

Output

fast product list
408 Took too long

A Caching Interceptor

@nestjs/cache-manager gives you CacheInterceptor, which caches GET responses by URL. (Redis setup is in the advanced level.)

Example

npm i @nestjs/cache-manager cache-manager

Example output

added 4 packages in 3s
import { CacheInterceptor, CacheKey, CacheModule, CacheTTL } from '@nestjs/cache-manager';
import { Controller, Get, Module, UseInterceptors } from '@nestjs/common';

@Controller('categories')
@UseInterceptors(CacheInterceptor)
export class CategoriesController {
  @Get()
  @CacheTTL(60_000)              // milliseconds in cache-manager v5+
  findAll() { return this.categories.findAll(); }
}

@Module({
  imports: [CacheModule.register({ ttl: 30_000 })],
  controllers: [CategoriesController],
})
export class CategoriesModule {}

Note: Only cache data that is the same for every user. Never cache /me or /cart by URL alone, or one user may see another user's data.