Why does it throw an error cannot read property of undefined (reading 'length') when running a GraphQL Subscription with Redis?

Viewed 114

Overview

  • Working on a NestJS project with GraphQL using a laptop with Window OS
  • Experimenting with GraphQL Subscriptions using graphql-redis-subscription@2.5.0 package
  • Redis is used in a docker container, see the docker-compose.yml below
  • The problem arose when the subscription postAdded is executed in GraphQL Playground. Instead of hanging to listen for events, it had crashed before I performed createPost mutation.

My code (I only include some important details)

posts.resolver.ts

import { Inject, UseGuards } from '@nestjs/common';
import { Args, Context, Mutation, Resolver, Subscription } from '@nestjs/graphql';
import { RedisPubSub } from 'graphql-redis-subscriptions';
import { GraphqlJwtAuthGuard } from '../auth/guards';
import { RequestWithUser } from '../auth/interfaces';
import { PUB_SUB } from '../pubsub/pubsub.module'

const POST_ADDED_EVENT = 'postAdded';

@Resolver(() => Post)
export class PostsResolver {
  constructor(
    private postsService: PostsService,
    @Inject(PUB_SUB) private pubSub: RedisPubSub,
  ) {}

  // my subscription (issue)
  @Subscription(() => Post)
  postAdded() {
    return this.pubSub.asyncIterator(POST_ADDED_EVENT);
  }

  // createPost method
  @Mutation(() => Post)
  @UseGuards(GraphqlJwtAuthGuard)
  async createPost(
    @Args('input') createPostInput: CreatePostInput,
    @Context() context: { req: RequestWithUser },
  ) {
    // just create a new post (assuming it works)
    const newPost = await this.postsService.create(
      createPostInput,
      context.req.user,
    );
    this.pubSub.publish(POST_ADDED_EVENT, { postAdded: newPost });
    return newPost;
  }
}

pubsub.module.ts

import { ConfigService } from '@nestjs/config';
import { RedisPubSub } from 'graphql-redis-subscriptions';
import { Global, Module } from '@nestjs/common';

export const PUB_SUB = 'PUB_SUB';

@Global()
@Module({
  providers: [
    {
      provide: PUB_SUB,
      useFactory: (configService: ConfigService) =>
        new RedisPubSub({
          connection: {
            host: configService.get('REDIS_HOST'),
            port: configService.get('REDIS_PORT'),
          },
        }),
      inject: [ConfigService],
    },
  ],
  exports: [PUB_SUB],
})
export class PubSubModule {}

app.module.ts

import { PubSubModule } from './pubsub/pubsub.module';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      playground: true,
      autoSchemaFile: path.join(process.cwd(), 'src/schema.gql'),
      installSubscriptionHandlers: true,
    }),
    PubSubModule,
    ConfigModule.forRoot({
      isGlobal: true,
      validationSchema: Joi.object({
        REDIS_HOST: Joi.string().required(),
        REDIS_PORT: Joi.number().required()
      }),
    }),
  ],
  providers: [AppService, AppResolver],
})
export class AppModule {}
version: '3'
services:
  redis:
    image: 'redis:alpine'
    ports:
      - '6379:6379'

  redis-commander:
    image: rediscommander/redis-commander:latest
    environment:
      - REDIS_HOSTS=local:redis:6379
    ports:
      - '8081:8081'
    depends_on:
      - redis

All the environment variables have already been defined in .env file.

REDIS_HOST="localhost"
REDIS_PORT=6379

When I run yarn start:dev and execute the subscription in GraphQL Playground

subscription {
  postAdded {
    id
    title
    paragraphs
  }
}

it raises an error like this:

{
  "errors": [
    {
      "message": "Cannot read properties of undefined (reading 'length')",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "postAdded"
      ]
    }
  ]
}

The terminal that monitors NestJS also raises an error like this:

[Nest] 8080  - 07/21/2022, 9:30:24 AM   ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'length')
TypeError: Cannot read properties of undefined (reading 'length')
    at JavascriptRedisParser.execute (C:\Users\HP\nestjs-project\node_modules\redis-parser\lib\parser.js:530:38)
    at Object.data (C:\Users\HP\nestjs-project\node_modules\ioredis\built\DataHandler.js:25:20)  
    at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:207:39)
    at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
    at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
    at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
    at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
    at TransformOperationExecutor.transform (C:\Users\HP\nestjs-project\node_modules\src\TransformOperationExecutor.ts:327:31)
    at ClassTransformer.instanceToPlain (C:\Users\HP\nestjs-project\node_modules\src\ClassTransformer.ts:25:21)
    at Object.classToPlain (C:\Users\HP\nestjs-project\node_modules\src\index.ts:23:27)

I have installed all the necessary dependencies like ioredis, graphql-redis-subscriptions and even graphql-subscriptions but the errors still exist. Redis also seems to be running properly.

I have tried reading the error logs but it did not occur in my source code and doing some research on StackOverFlow but none seems to have solved the problem.

1 Answers

Are you by any chance using a global ClassSerializerInterceptor?? Because I was running into the same problem just today and I solved by removing the interceptor. It happened because the subscription needs to receive an instance of AsyncIterable but the class serializer turns it into a plain object.

Apart from that I would recommend you change the GraphQl config, remove the installSubscriptionHandlers and change the config like this:

GraphQLModule.forRoot<ApolloDriverConfig>({
  driver: ApolloDriver,
  playground: true,
  autoSchemaFile: path.join(process.cwd(), 'src/schema.gql'),
  // remove this option: 
  // installSubscriptionHandlers: true,
  // add the following:
  subscriptions: {
    "graphql-ws": true // or config object
  }
}),

You can read more about it in the nestjs docs

I hope this solves your problem.

Related