Search code examples
graphqlnestjssubscriptiondataloader

Nest.JS graphql subscription does not work with DataLoader (context is not defined)


I have a nestjs graphql application which uses DataLoader. It works fine for queries and mutations, but it does not work for subscriptions. This is how configuration is defined in app.module:

@Module({
  imports: [
    ...
    GraphQLModule.forRootAsync({
      driver: ApolloDriver,
      imports: [TasksModule],
      inject: [TasksService],
      useFactory: (tasksService: TasksService) => ({
        playground: true,
        autoSchemaFile: join(process.cwd(), 'src/schema.gql'),
        context: () => createTaskLoaders(tasksService),
        subscriptions: {
          'graphql-ws': true,
          'subscriptions-transport-ws': true
        }
      })
    })
  ]
})
export class AppModule {}

This is how context is used in the resolver:

  @ResolveField()
  async fieldSet(@Parent() task: Task, @Context() context) {
    console.log('context', Object.keys(context)) // The output is empty when it runs for subscription
    return context.someCustomLoader.load(task.id)
  }

It does not matter what createTaskLoaders and someCustomLoader are - they are just custom functions. The issue is that they are not passed to the context when it's used from inside a subscription. I guess some additional configuration is needed for subscriptions case, but I can not find any working nestjs dataloader + subscriptions example as well as documentation about context for subscriptions.


Solution

  • You have to pass the loaders manually in subscriptions using the onConnect

      subscriptions: {
        'graphql-ws': {
          path: '/graphql',
          onConnect: (context: Context) => {
            const { connectionParams, extra } = context;
    
            extra.loaders = createTaskLoaders(tasksService);
          },
        },
        'subscriptions-transport-ws': {
          path: '/graphql',
          onConnect: (connectionParams) => {
            return {
              loaders: createTaskLoaders(tasksService),
            };
          },
        },
      },
    

    Something like this should work