Search code examples
nestjspeer-dependencies

Nest can't resolve dependencies of ThrottlerGuard


I have 2 repos, one is my host repo, and one is where my throttler is present. In the host repo I have my throttler as a dependency in my package.json.

I have 2 files in the throttler package:

GqlThrottlerGuard:

@Injectable()
export class GqlThrottlerGuard extends ThrottlerGuard {
    getRequestResponse(context: ExecutionContext): { res: Response; req: Request } {
        if (context.getType<GqlContextType>() === 'graphql') {
            const gqlCtx = GqlExecutionContext.create(context);
            const ctx = gqlCtx.getContext();

            return { req: ctx.req, res: ctx.req.res };
        }

        const ctx = context.switchToHttp();
        return { req: ctx.getRequest(), res: ctx.getRequest() };
    }
}

GraphqlThrottlerModule:

@Module({
    imports: [
        ThrottlerModule.forRootAsync({
            imports: [ConfigModule],
            inject: [ConfigService],
            useFactory: (config: ConfigService) => [
                {
                    ttl: +config.get('THROTTLE_TTL'),
                    limit: +config.get('THROTTLE_LIMIT'),
                },
            ],
        }),
    ],
    providers: [GqlThrottlerGuard],
    exports: [GqlThrottlerGuard],
})
export class GraphqlThrottlerModule {}

And my throttler package.json:

{
    "name": "my-own-throttler",
    "version": "1.0.0",
     ...
    "dependencies": {
        "@nestjs/config": "^3.1.1",
        "@nestjs/core": "^9.0.0",
        "@nestjs/graphql": "^11.0.6",
        "@nestjs/throttler": "^5.0.0",
        "rxjs": "^7.8.1"
    }
}

In my host repo I'm using my throttler like this:

app.module:

@Module({
    imports: [
        ...
        GraphqlThrottlerModule,
    ],
    providers: [
        {
            provide: APP_GUARD,
            useClass: GqlThrottlerGuard,
        },
    ],
})
export class AppModule {}

package.json:

{
    "name": "my-host",
    "version": "1.0.0",
     ...
    "dependencies": {
        "@nestjs/common": "^9.0.0",
        "@nestjs/core": "^9.0.0",
        "my-own-throttler": "1.0.0",
        "reflect-metadata": "^0.1.13",
        "rxjs": "^7.2.0"
    }
}

When i'm running my app, it shows me this error:

 Nest can't resolve dependencies of the GqlThrottlerGuard (THROTTLER:MODULE_OPTIONS,
Symbol(ThrottlerStorage), ?).
 Please make sure that the argument Reflector at index [2] is 
available in the GraphqlThrottlerModule context.

When I put the same throttler code in the host repo, it works, but when I put it as a dependency in my package.json it does not work.

I suspected it might be a peer dependency issue, but when I tried to put the throttler as a peer dependency in the throttler package.json it still did not work. I also tried to install @nestjs/[email protected] which uses nestjs 9, but it still didn't work.


Solution

  • In your my-own-throttler package, when publishing the @nestjs/ packages should be set to peerDependencies, except for maybe @nestjs/throttler if you aren't going to install that directly in your main project. The reason being is that each node_modules/@nestjs/common and node_modules/@nestjs/core have their own class references to things like Reflector and ModuleRef which Nest will eventually being doing checks of injectedDependency instanceof Reflector and if the wrong Reflector is pulled when instantiating the dependency, or during the instanceof check, it fails, and Nest will no longer be able to resolve the injected depdnency and the application will fail to start.

    This is a side effect of JS Realms.

    As @nestjs/throttler already sets @nestjs/common and @nestjs/core to peerDeps, you should be okay with keeping it as a dependency, but as a precaution, I'd probably just set it to a peerDependency and be done with it. That way, if you need to make use of @ThrottleSkip() or the @Throttle() decorators you are able to without worry.


    Also, very important, make sure you are not installing this package locally (using an npm link or npm install ../package as that will not properly respect the peer dependnecies. Either make a tarball using npm pack and install the tar file directly, copy the package.json and the dist to node_modules/<my-package>, or publish the package and install it with the usual npm i <my-package>