NestJS Interceptor use case: Gathering data and storing it into MongoDB TimeSeries Collection.

Introduction

Data is the new gold. The more you have it, the more you can do with it.

One use case is for better understanding your users or providing insights in their current activities to optimize their workflows and get better at daily work. Understanding and leveraging data can significantly enhance decision-making processes and automate numerous tasks.

Data is analyzed by analysts to help businesses and organizations to predict market trends, understand customer behaviour.

Overview

For the sake of this example we're going to intercept the request and collect data about that request and then we're going to store data inside the MongoDB time series collection.

What is interceptor?

NestJS interceptors enables developers to execute additional logic before or after a method is called in a controller. They are ideal for cross-cutting concerns, such as logging, transforming responses, adding additional headers, or even managing complex data flows. Thing to note of NestJS request lifecycle: interceptors execute before pipes, but after guards.

Defining TimeSeries schema for analytics in MongoDB.

Let's create define a schema for time series collection in MongoDB by utilizing @Schema decorator.

import { Prop, Schema, SchemaFactory, raw } from '@nestjs/mongoose';

@Schema({
  versionKey: false,
  timestamps: true, // this will create createdAt field in collection
  collection: 'time_series_schema',
  timeseries: {
    timeField: 'createdAt', 
    granularity: 'minutes', 
  },
})
export class TimeSeriesSchema {
    // other properties go here
}

Don't forget to registister it with MongooseModule.forFeature(). There are some limitations with MongoDB time series collections make sure to check them out here. You'd need to implement service and/or repository to insert data. This step depends on your architecture.

Intercepting the request with NestJS interceptor.

To collect route of the request we first must get the request instance by switching to the http context in NestJS. Interceptors also in different contexts as well.

In this short interceptor we do the following:

  • Switch to request context
  • Structure data object as we see fit. In this example we're using express. If you're using something else the meta data about the request might differ. (1)
    also type Request is imported from import { Request } from 'express';
  • since handle() is an observable, we pipe and tap into the stream and insert data into MongoDB. This part is important, this way the data insertion won't slow down the request processing.

    Code for reference here:

// ... other imports.
import { Request } from 'express';

@Injectable()
export class CollectRouteTimeSeriesDataInterceptor
    implements NestInterceptor {
    constructor(
        private readonly urlRouteService: UrlRouteService,
    ) { }

    intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        const request = context.switchToHttp().getRequest<Request>();
        
        const data = {
            ip: request.ip,
            userAgent: request.get('User-Agent'),
            referrer: request.get('Referrer'),
            url: request.originalUrl,
            method: request.method,
            headers: request.headers,
            body: request.body,
            query: request.query,
          };
        
        const route = request.params.hash;
        return next
            .handle()
            .pipe(tap(() => this.urlRouteService.recordVisit(route, data)));
    }
}

and let's apply interceptor where we need. In this example we're going to use it on Controller method.

 @Get('visit/:hash')
 @UseInterceptors(CollectRouteTimeSeriesDataInterceptor)
  async getAll(
    @Param('hash') hash: string
  ) {
    // process the request.     
  }

So each time request would hit this route it would insert request metadata in the MongoDB time series database.

Conclusion

In this short overview we have learned how to use interceptors to gather request metadata and store it inside MongoDB time series database. This could help to capture valuable metadata and store it for further processing.