Search code examples
graphqlpermissionsnestjsauthorizationapollo-server

NestJS - How can dataloaders be used in combination with GraphQL Shield?


I recently found that because I have asynchronous calls being made in some of my shield rules, it's causing my dataloader batch functions to be called multiple times, when they should only be called once, which leaves me with the N+1 problem.

I believe this is due to how the dataloader library requires that all of the batch function calls occur during the same event loop "tick" (source), and the asynchronous calls made in my shield rules are preventing this.

Here's the useFactory function that I pass to GraphQLModule in my app:

  useFactory(dataloaderService: DataloaderService, usersService: UsersService) {
    return {
      context: async ({ req }: { req: Request }) => {
        const loaders = dataloaderService.getLoaders();
        const services = { usersService };
        return { loaders, services };
      },
      transformSchema: (schema: GraphQLSchema) => {
        schema = applyMiddleware(schema, shieldPermissions);
        return schema;
      },
    };
  },

Is there any way to use both dataloaders and an authorization library like GraphQL Shield at the same time? Is there anything wrong with my current configuration that could be causing this?


Solution

  • Passing an option for batchScheduleFn resolved the issue:

      private _createGroupsLoader() {
        return this._getDataLoader<number, Group>(
          async (groupIds) =>
            this.groupsService.getGroupsBatch(groupIds as number[]),
          {
            batchScheduleFn: (callback) => setTimeout(callback, 5),
          }
        );
      }
    

    Without passing batchScheduleFn, dataloader uses it's own scheduler internally that either uses process.nextTick or setImmediate.

    Reason for using 5 ms:

    Our current heuristic is to yield execution back to the main thread every 5ms. 5ms isn't a magic number, the important part is that it's smaller than a single frame even on 120fps devices, so it won't block animations.

    Source: https://dev.to/tsirlucas/integrating-dataloader-with-concurrent-react-53h1