npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

nestjs-revalidate

v0.2.0

Published

Declarative HTTP revalidation for NestJS with ETag, Last-Modified, Cache-Control, Vary and 304 responses.

Readme

nestjs-revalidate

Declarative HTTP revalidation for NestJS.

nestjs-revalidate helps NestJS endpoints use proper HTTP revalidation semantics with decorators instead of manual header handling in controllers.

It supports:

  • ETag
  • Last-Modified
  • If-None-Match
  • If-Modified-Since
  • 304 Not Modified
  • Cache-Control
  • Vary

Works with both Express and Fastify.

Pre-release: the package is still being finalized and may change before the first public npm release.

What this package is for

A common pattern in NestJS apps looks like this:

  • fetch resource
  • compute ETag
  • set headers
  • read If-None-Match
  • compare validators
  • return 304 when nothing changed

Without a reusable abstraction, this logic usually ends up duplicated across controllers.

nestjs-revalidate moves that logic into decorators and a single interceptor-driven runtime path.

What this package is not

This package is not:

  • a Redis cache
  • a memory response cache
  • an invalidation system
  • a cache-manager replacement

It focuses on HTTP revalidation, not cache storage.

Installation

After publishing:

npm install nestjs-revalidate

Peer dependencies:

  • @nestjs/common
  • @nestjs/core
  • reflect-metadata
  • rxjs

Quick start

Register the module once:

import { Module } from '@nestjs/common';
import { RevalidateModule } from 'nestjs-revalidate';

@Module({
  imports: [
    RevalidateModule.forRoot({
      etag: {
        mode: 'weak',
      },
      onProjectorError: 'throw',
      setHeadersOnNotModified: true,
    }),
  ],
})
export class AppModule {}

Use @HttpCache(...) on GET or HEAD endpoints:

import { Controller, Get, Param } from '@nestjs/common';
import { HttpCache } from 'nestjs-revalidate';

interface UserDto {
  id: string;
  version: number;
  updatedAt: Date;
  name: string;
}

@Controller('users')
export class UsersController {
  @Get(':id')
  @HttpCache<UserDto>({
    etag: (user) => user.version,
    lastModified: (user) => user.updatedAt,
    cacheControl: 'private, max-age=0, must-revalidate',
    vary: ['Accept-Encoding'],
  })
  findOne(@Param('id') id: string): UserDto {
    return {
      id,
      version: 7,
      updatedAt: new Date('2026-04-16T12:00:00.000Z'),
      name: 'Alex',
    };
  }
}

Low-level decorators

You can also compose the behavior explicitly:

import { Controller, Get, Param } from '@nestjs/common';
import {
  EtagBy,
  LastModifiedBy,
  CacheControl,
  Vary,
} from 'nestjs-revalidate';

interface UserDto {
  id: string;
  version: number;
  updatedAt: Date;
  name: string;
}

@Controller('users')
export class UsersController {
  @Get(':id')
  @EtagBy((user: UserDto) => user.version)
  @LastModifiedBy((user: UserDto) => user.updatedAt)
  @CacheControl('private, max-age=0, must-revalidate')
  @Vary('Accept-Encoding')
  findOne(@Param('id') id: string): UserDto {
    return {
      id,
      version: 7,
      updatedAt: new Date('2026-04-16T12:00:00.000Z'),
      name: 'Alex',
    };
  }
}

Public API

@HttpCache(options)

High-level decorator that combines multiple revalidation settings.

@HttpCache({
  etag: (value) => value.version,
  lastModified: (value) => value.updatedAt,
  cacheControl: 'private, max-age=0, must-revalidate',
  vary: ['Accept-Encoding'],
})

@EtagBy(projector, mode?)

Defines how to compute the resource validator used for ETag.

@EtagBy((value) => value.version)

@LastModifiedBy(projector)

Defines how to compute Last-Modified.

@LastModifiedBy((value) => value.updatedAt)

@CacheControl(value)

Sets Cache-Control for the endpoint.

@CacheControl('private, max-age=0, must-revalidate')

@Vary(...headers)

Sets the Vary header.

@Vary('Accept-Encoding', 'Accept-Language')

@NoStore()

Forces Cache-Control: no-store.

@NoStore()

Module options

export type RevalidateProjectorErrorMode = 'throw' | 'skip';

export interface RevalidateModuleOptions {
  onProjectorError?: RevalidateProjectorErrorMode;
  setHeadersOnNotModified?: boolean;
  etag?: {
    mode?: 'weak' | 'strong';
  };
}

onProjectorError

Controls what happens when a projector throws.

  • 'throw': propagate the error
  • 'skip': ignore the failing projector and continue with the rest

Example:

RevalidateModule.forRoot({
  onProjectorError: 'skip',
})

setHeadersOnNotModified

Controls whether computed headers are included on 304 Not Modified responses.

  • true: include computed headers on 304
  • false: omit them on 304

Example:

RevalidateModule.forRoot({
  setHeadersOnNotModified: true,
})

etag.mode

Configures the default ETag mode.

  • 'weak'
  • 'strong'

Example:

RevalidateModule.forRoot({
  etag: {
    mode: 'weak',
  },
})

Runtime behavior

For GET and HEAD requests, the interceptor:

  1. executes the route handler
  2. computes validators from the returned value
  3. reads conditional request headers
  4. decides whether the resource changed
  5. sets response headers
  6. returns 304 Not Modified when appropriate

For non-GET/HEAD requests, revalidation logic is skipped.

Supported platforms

  • NestJS + Express
  • NestJS + Fastify

Current scope

The package intentionally stays small.

Current focus:

  • predictable ETag behavior
  • predictable Last-Modified behavior
  • correct 304 handling
  • Express/Fastify support
  • small and explicit API surface

Limitations

Current limitations and expectations:

  • designed for GET and HEAD
  • not intended as a storage-backed cache
  • does not automatically invalidate data after writes
  • does not eliminate database work by itself
  • not intended for streaming or file-specific behavior in its current form

Testing

The project currently includes:

  • unit tests for core revalidation logic
  • metadata and decorator tests
  • e2e tests for Express
  • e2e tests for Fastify

License

MIT