G. Hruschka
G. Hruschka

Reputation: 934

Nest JS's Throttler Guard from REST module is breaking GraphQL module

I have a Nest.js app with a REST module and a GraphQL module. Both are imported into an App.module.ts. I'm using Nest's Throttler Guard to protect the whole application. As it's already known, GraphQL does not work with the normal ThrottlerGuard, so I created a GqlThrottlerGuard and imported it on the GraphQL module, while importing the original ThrottlerGuard on the REST module.

So, my graphQL module looks like this:

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: true
    }),
    ThrottlerModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        ttl: config.get('security.throttle.ttl'),
        limit: config.get('security.throttle.limit'),
      }),
    }),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: GqlThrottlerGuard,
    },
  ],
})
export class GraphModule { }

And the REST module, like this:

@Module({
  imports: [
    ThrottlerModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        ttl: config.get('security.throttle.ttl'),
        limit: config.get('security.throttle.limit'),
      }),
    }),
  ],
  controllers: [RestController],
  providers: [
    {
      provide: APP_GUARD,
      useClass: ThrottlerGuard,
    },
  ],
})
export class RestModule implements NestModule {}

Finally, both modules are imported to the App Module, which is the module I actually run:

@Module({
  imports: [
    RestModule,
    GraphModule,
  ],
})
export class AppModule { }

For some reason, the error seen here is still happening to me on the GraphModule, even though the normal ThrottlerGuard is imported only on the RestModule. Should it work like this? How can I solve it?

Upvotes: 2

Views: 2791

Answers (1)

Jay McDoniel
Jay McDoniel

Reputation: 70412

APP_GUARD is a global binding, it applies to all routes. You should make one coherent guard that returns the proper request response based on the ExecutionContext#getType method which will either return http or graphql

@Injectable()
export class CustomThrottlerGuard extends ThrottlerGuard {

  getRequestResponse(context: ExecutionContext) {
    const reqType = context.getType<ContextType | 'graphql'>()
    if (reqType === 'graphql') {
      const gqlCtx = GqlExecutionContext.create(context);
      const ctx = gqlCtx.getContext();
      return { req: ctx.req, res: ctx.res };
    } else if (reqType === 'http') {
      return {
        req: context.switchToHttp().getRequest(),
        res: context.switchToHttp().getResponse()
      }
    } else {
      // handle rpc and ws if you have them, otherwise ignore and make previous `else if` just an `else`
    }
  }

Upvotes: 5

Related Questions