Interceptors, guards, pipes, and exception filters are sometimes referred to as "enhancers" in the NestJS documentation. Although they are all declared with the @Injectable()
decorator and can use Dependency Injection, they are not providers. Therefore, they cannot be exported from the module like normal providers.
This article uses NestJS v10.1 and Ditsmod v2.38 for comparison. I am the author of Ditsmod.
I decided to write this article while browsing NestJS issues, in particular A guard with dependencies can't be injected into another module. Its author wonders why NestJS cannot resolve the dependency for AuthGuard because this dependency is imported into the module where the guard is declared.
And the creator of NestJS wrote the following answer:
Enhancers can’t be injected to other providers. Hence, they are not providers. Likewise, enhancers can be tied & injected to the method evaluation context, but you can’t bind providers this way. There’s no reason to change this, especially if you start thinking about enhancers scoped per host (module in which they exist). Adding them to the providers array explicitly creates an unnecessary redundancy and confusion (since enhancers don’t act equally to providers).
So, according to Kamil, adding interceptors, guards, pipes and exception filters to the list of providers will confuse NestJS users. As a result of these restrictions, NestJS applications must export all dependencies for guards from the module, even if those dependencies are not directly used outside the module in which the guards are declared. This restriction essentially breaks module encapsulation.
In Ditsmod, the guards, interceptors, and controller error handlers (analogous to exception filters in NestJS) are normal providers that are passed to the injector at the request level. In next code demonstrates the centralized addition of a guard on OtherModule
:
import { featureModule } from '@ditsmod/core';
import { OtherModule } from '../other/other.module';
import { AuthModule } from '../auth/auth.module';
import { AuthGuard } from '../auth/auth.guard';
@featureModule({
imports: [
AuthModule,
{ path: 'some-path', module: OtherModule, guards: [AuthGuard] }
]
})
export class SomeModule {}
In this example, the AuthModule
exports the AuthGuard
so that the current module can use this guard as a regular provider.
Also published here.