Search code examples
node.jsnestjs

Using an interceptor exported from a module using module service


I created an Interceptor with a related module so that I could manage its imports without having to import them into the AppModule.

@Injectable()
export class RequestResponseLoggerInterceptor implements NestInterceptor {
    constructor(configService: ConfigService) {
        const env = configService.getOrThrow('NECESSARY_ENV');

        // do stuff...
    }

    intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> | Promise<Observable<any>> {
        // do stuff...
    }
}
import {Module} from '@nestjs/common';
import {ConfigModule} from '@nestjs/config';
import {
    RequestResponseLoggerInterceptor
} from '@app/interceptor-request-response-logger/request-response-logger.interceptor';
import envConfig from '@app/interceptor-request-response-logger/config/env.config';

@Module({
    imports: [ConfigModule.forFeature(envConfig)],
    providers: [RequestResponseLoggerInterceptor],
    exports: [RequestResponseLoggerInterceptor]
})
export class RequestResponseLoggerModule {
}

I also had to add the ConfigModule.forRoot() imports in AppModule otherwise the ConfigModule.forFeatures(...) in the RequestResponseLoggerModule would not work.

import {Module} from '@nestjs/common';
import {SearchController} from './controllers/search.controller';
import {SearchService} from './services/search.service';
import {RequestResponseLoggerModule} from '@app/interceptor-request-response-logger';
import {ConfigModule} from '@nestjs/config';

@Module({
    imports: [
        ConfigModule.forRoot(),
        RequestResponseLoggerModule
    ],
    controllers: [SearchController],
    providers: [
        SearchService
    ]
})
export class AppModule {
}

Before I get into some abyss trying to get something unintended to work I would like to ask if it makes sense to have such an approach or if Interceptors inherit the module and thus the Module imports into which they are imported.

The project I'm working on will have a lot of these Interceptors that I'll need to reuse on various apps, and that's why I'm trying to figure out how to start with the design of my codebase correctly.


Solution

  • Rather than providing and exporting the interceptor, the module can import and export whatever modules it needs to, as well as provide and export whatever providers the interceptor depends on. Interceptors are a special psuedo-provider referred to as "enhancers", that follow most but not all of the rules that regular providers do. Other enhancers include guards, pipes, and filters, just as an fyi.

    Enhancers cannot be provided through the providers array unless you plan to use the APP_INTERCEPTOR provider and use the useExisting custom provider option.

    So, in your case, it looks like your RequestResponseLoggerModule should look like

    import {Module} from '@nestjs/common';
    import {ConfigModule} from '@nestjs/config';
    import envConfig from '@app/interceptor-request-response-logger/config/env.config';
    
    @Module({
        imports: [ConfigModule.forFeature(envConfig)],
        providers: [],
        exports: [ConfigModule]
    })
    export class RequestResponseLoggerModule {
    }
    

    And now, in whatever modules you want to use the RequestResponseLoggerInterceptor you can import the RequestResponseLoggerModule and @UseInterceptor(RequestResponseLoggerInterceptor) with all of its config in place and ready to be made use of.