typescript 59 lines · 7 steps

HTTP conditional caching in a NestJS controller

A controller endpoint returns 304 Not Modified when a client's cached copy is still fresh, using ETag and Last-Modified validators.

Explained by highlit
1import {
2 Controller,
3 Get,
4 Param,
5 Header,
6 Req,
7 Res,
8 NotFoundException,
9} from '@nestjs/common';
10import { Request, Response } from 'express';
11import { createHash } from 'crypto';
12import { ArticlesService } from './articles.service';
13 
14@Controller('articles')
15export class ArticlesController {
16 constructor(private readonly articles: ArticlesService) {}
17 
18 @Get(':slug')
19 @Header('Cache-Control', 'public, max-age=60, stale-while-revalidate=300')
20 async findOne(
21 @Param('slug') slug: string,
22 @Req() req: Request,
23 @Res() res: Response,
24 ): Promise<void> {
25 const article = await this.articles.findBySlug(slug);
26 if (!article) {
27 throw new NotFoundException(`Article "${slug}" not found`);
28 }
29 
30 const etag = this.weakEtag(article.id, article.updatedAt);
31 const lastModified = article.updatedAt.toUTCString();
32 
33 res.setHeader('ETag', etag);
34 res.setHeader('Last-Modified', lastModified);
35 res.setHeader('Vary', 'Accept-Encoding');
36 
37 const ifNoneMatch = req.headers['if-none-match'];
38 const ifModifiedSince = req.headers['if-modified-since'];
39 
40 const etagMatches = ifNoneMatch === etag;
41 const notModifiedSince =
42 ifModifiedSince !== undefined &&
43 new Date(ifModifiedSince).getTime() >= article.updatedAt.getTime();
44 
45 if (etagMatches || notModifiedSince) {
46 res.status(304).end();
47 return;
48 }
49 
50 res.status(200).json(article);
51 }
52 
53 private weakEtag(id: string, updatedAt: Date): string {
54 const digest = createHash('sha1')
55 .update(`${id}:${updatedAt.getTime()}`)
56 .digest('base64');
57 return `W/"${digest}"`;
58 }
59}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Conditional requests let a server skip resending a body when the client already holds a current copy.
  2. 2A weak ETag derived from stable identity fields lets you compare versions without hashing the full payload.
  3. 3Escaping to Express's raw response with @Res means you own status codes and body serialization for that handler.

Related explainers

Share this explainer

Here's the card — post it anywhere.

HTTP conditional caching in a NestJS controller — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code