Implementing Request Header-Based Version Detection in NestJS — Using an Interceptor

In a recent project, we needed to add version control functionality to the API to ensure that different versions of the client could handle interface changes while prompting older clients to update when necessary. This is a common requirement in mobile development, where the client typically sends the current version number via the HTTP request header, and the server returns the corresponding response data based on that version number.

The issue we encountered was that in some cases, when the client version was too outdated, we wanted to attach an update prompt to the normal return data. This would allow the interface request side to handle this logic uniformly without changing the original business logic. To address this, we could use an interceptor or middleware in NestJS.

This article takes the interceptor as an example to guide you through the implementation. For middleware implementation, refer to this article: “Implementing Request Header-Based Version Detection in NestJS — Using Middleware”

Requirement Analysis

The requirement is simple: the client version number is carried in the header of each request, and the server checks this version number. If the client’s version is lower than the latest version required by the server, an update prompt needs to be included in the response; if the version is valid, the data is returned normally without modification.

How to Implement It

In NestJS, we can capture both requests and responses via an interceptor. In the interceptor, we can:

  1. Obtain the version information from the request header.
  2. Determine if an update is required by a simple version number comparison.
  3. Modify the response data by adding an extra field if an update is needed.

1. Creating a Version Detection Interceptor

Interceptors are essential tools in NestJS for intercepting requests and responses. In this scenario, we need to create an interceptor responsible for version detection and processing before the response data is sent back to the client.

1
import {
2
CallHandler,
3
ExecutionContext,
4
Injectable,
5
NestInterceptor,
6
} from "@nestjs/common";
7
import { Observable } from "rxjs";
8
import { map } from "rxjs/operators";
9
10
@Injectable()
11
export class VersionInterceptor implements NestInterceptor {
12
private readonly latestVersion = "2.0.0"; // Set the latest version
13
14
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
15
const request = context.switchToHttp().getRequest();
16
const version = request.headers["version"]; // Get the version from the header
17
18
return next.handle().pipe(
19
map((data) => {
20
if (version && this.needsUpdate(version)) {
21
// Add extra fields if an update is needed
22
return {
23
...data,
24
updateAvailable: true,
25
updateUrl: "xxxx", // Update URL
26
latestVersion: this.latestVersion,
27
message: "A new version is available, please update.",
28
};
29
}
30
// Return the original response data if no update is needed
31
return data;
32
}),
33
);
34
}
35
36
// Version comparison logic
37
private needsUpdate(clientVersion: string): boolean {
38
return clientVersion < this.latestVersion;
39
}
40
}

In this interceptor, we use request.headers['version'] to get the version number sent by the client and call the needsUpdate method to compare versions. If the client version is outdated, we insert an additional updateAvailable field into the returned data to inform the user that a new version is available.

2. Applying the Interceptor

There are two ways to apply this interceptor in NestJS: local or global.

Applying the Interceptor Locally

If the version detection function is needed only on certain routes, you can apply the interceptor in the corresponding controller.

1
import { Controller, Get, UseInterceptors } from "@nestjs/common";
2
import { VersionInterceptor } from "./version.interceptor";
3
4
@Controller("api")
5
export class AppController {
6
@Get("data")
7
@UseInterceptors(VersionInterceptor)
8
getData() {
9
return {
10
data: "Here is your data",
11
};
12
}
13
}

Applying the Interceptor Globally

If you want all API endpoints to support version detection, you can apply the interceptor globally via main.ts.

1
import { NestFactory } from "@nestjs/core";
2
import { AppModule } from "./app.module";
3
import { VersionInterceptor } from "./version.interceptor";
4
5
async function bootstrap() {
6
const app = await NestFactory.create(AppModule);
7
8
// Apply the interceptor globally
9
app.useGlobalInterceptors(new VersionInterceptor());
10
11
await app.listen(3000);
12
}
13
bootstrap();

3. Testing the API

After setting up the interceptor, we can test the API using curl or Postman. The client sends the version information via the request header, and the server returns the corresponding response based on the version number.

Example Request

Terminal window
1
curl -X GET http://localhost:3000/api/data -H "version: 1.0.0"

Example Response (When an update is needed)

1
{
2
"data": "Here is your data",
3
"updateAvailable": true,
4
"latestVersion": "2.0.0",
5
"message": "A new version is available, please update."
6
}

Example Response (When no update is needed)

1
{
2
"data": "Here is your data"
3
}

Conclusion

Interceptors are a highly efficient and flexible tool for handling version detection. They allow us to dynamically modify response data without altering the controller logic, returning different prompt information based on the client’s version. This approach is not only suitable for version control but also for other similar scenarios, such as globally formatting response content or adding extra metadata.

This method helps maintain API compatibility while ensuring that old versions of the client receive update prompts in time, improving the user experience.