How to set Cache-Control header correctly in node.js + express.js

The question you are seeking an answer to :

  • How can I set Cache-Control in Node.js?
  • How can I enable browser caching in Express.js?

The key is to properly get and set the following headers.

  • Cache-Control header
  • ETag header
  • Vary header
  • If-None-Match header

This article explains how to do this with code examples.

1. Set Cache-Control header

First of all, you need to set Cache-Control header.

What is the Cache-Control header? :

The Cache-Control HTTP header field holds directives (instructions) — in both requests and responses — that control caching in browsers and shared caches (e.g. Proxies, CDNs).

Quote : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control

The Cache-Control header has many directives.

But now all that is needed are the below directives.

Cache-Control: max-age=31557600, no-cache

In the above header, the max-age=N (e.g. N=31557600) directive means that the response remains fresh until N seconds after responsed and the no-cahce directive means the response must be validated with the server before each reuse.

 

The Cache-Control header in Node.js is set like this.

Sample code in Node.js + Express.js :

const express = require('express');

const app = express();

app.use((req, res, next)=>{
  switch(true){
    case req.url.endsWith('.css'):
    case req.url.endsWith('.js'):
      /// Enable browser cache for .css / .js
      
      /// 1. Set Cache-Control and Vary header
      res.setHeader("Cache-Control", "max-age=31557600, no-cache");
      res.setHeader('Vary', 'ETag, Content-Encoding');
      
      /// To be continued later...
      
      break;
  }
});

Just set Cache-Control and Vary header like the above. Note that 31557600 seconds represents one year, so the browser cache is valid for one year unless the response / file is updated.

2. Create file last-modified date hash

Next, generate a hash of the file last-modified date.

Method to create SHA1 hash :

function createHash = (str)=>{
  const crypto = require('crypto');
  const hash = crypto.createHash('sha1');
  hash.update(str)
  return hash.digest('hex')
};

Generate a hash of file last modified date :

const express = require('express');

const app = express();

app.use((req, res, next)=>{
  switch(true){
    case req.url.endsWith('.css'):
    case req.url.endsWith('.js'):
      /// Enable browser cache for .css / .js
      
      /// 1. Set Cache-Control and Vary header
      /// ...
      
      /// 2. Create a hash of the file last-modified date.
      const stats = fs.statSync('public'+req.url);
      const mtime = stats.mtime;
      const mtimeHash = createHash(mtime.toString());
      
      /// To be continued later...
      
      break;
  }
});

The file last-modified date can be obtained by using the fs module. However, it is assumed that files loaded from the outside are placed directly under the [public] directory.

3. Set file lastmod hash in ETag header

Set ETag to a hash of the file’s last modified date.

Sample code in Node.js + Express.js :

const express = require('express');

const app = express();

app.use((req, res, next)=>{
  switch(true){
    case req.url.endsWith('.css'):
    case req.url.endsWith('.js'):
      /// Enable browser cache for .css / .js
      
      /// 1. Set Cache-Control and Vary header
      /// ...
      
      /// 2. Create a hash of the file last-modified date.
      /// ...
      
      /// 3. Set file lastmod hash in ETag header
      res.setHeader('ETag', mtimeHash);
      
      /// To be continued later...
      
      break;
  }
});

What is the ETag header? :

The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource. It lets caches be more efficient and save bandwidth, as a web server does not need to resend a full response if the content was not changed.

Quote : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag

The ETag values are the same if the response has not been changed.

4. Compare ETag and If-None-Match

Compare ETag header’s and If-None-Match header’s value.

Sample code in Node.js + Express.js

const express = require('express');

const app = express();

app.use((req, res, next)=>{
  switch(true){
    case req.url.endsWith('.css'):
    case req.url.endsWith('.js'):
      /// Enable browser cache for .css / .js
      
      /// 1. Set Cache-Control and Vary header
      /// ...
      
      /// 2. Create a hash of the file last-modified date.
      /// ...
      
      /// 3. Set file lastmod hash in ETag header
      /// ...
    
      /// 4. Compare ETag and If-None-Match
      const ifNoneMatch = req.headers['if-none-match'];
      if (ifNoneMatch && ifNoneMatch === mtimeHash) {
        // Content is cached, so return '304 Not Modified'.
        res.statusCode = 304;
        res.end();
        return;
      }
      
      break;
  }
});

What is the If-None-Match header?? :

The If-None-Match HTTP request header makes the request conditional. For GET and HEAD methods, the server will return the requested resource, with a 200 status, only if it doesn’t have an ETag matching the given ones. For other methods, the request will be processed only if the eventually existing resource’s ETag doesn’t match any of the values listed.

Quote : https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match

If ETag’s value is same as If-None-Match’s value, which returned from request, it means the respons / file is not modified. If not, origin server return updated response.

Entire code for the setting Cache-Control

The overall code is here :

const express = require('express');

const app = express();

app.use((req, res, next)=>{
  switch(true){
    case req.url.endsWith('.css'):
    case req.url.endsWith('.js'):
      /// Enable browser cache for .css / .js
      
      /// 1. Set Cache-Control and Vary header
      res.setHeader("Cache-Control", "max-age=31557600, no-cache");
      res.setHeader('Vary', 'ETag, Content-Encoding');
      
      /// 2. Create a hash of the file last-modified date.
      const stats = fs.statSync('public'+req.url);
      const mtime = stats.mtime;
      const mtimeHash = createHash(mtime.toString());
      
      /// 3. Set file lastmod hash in ETag header
      res.setHeader('ETag', mtimeHash);
    
      /// 4. Compare ETag and If-None-Match
      const ifNoneMatch = req.headers['if-none-match'];
      if (ifNoneMatch && ifNoneMatch === mtimeHash) {
        // Content is cached, so return '304 Not Modified'.
        res.statusCode = 304;
        res.end();
        return;
      }

      break;
  }
});

Good bye! 🙂 🙂

Leave a Reply