Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apollo server subscription nested within a type

I'm trying to get the resolver for a subscription working in Apollo Server 2. The subscription works when it is a top-level field (i.e. directly under Subscription at the schema root).

However, if the subscription is contained within another type, I always get the error Subscription field must return Async Iterable. Received: undefined on the client's websocket connection -- the server's resolver is never executed.

i.e. this schema works:

type Subscription {
  postAdded: Post
}

but this one does not:

type Subscription {
  post: PostSubscription
}

type PostSubscription {
  postAdded: Post
}

My resolver for the second case looks like this, but I've tried a bunch of different variations with no success:

Subscription: {
  post: () => ({
    PostSubscription: {}
  })
},
PostSubscription: {
  postAdded: {
    subscribe: () => pubSub.asyncIterator(['postAdded'])
  }
}
like image 997
Raman Avatar asked Apr 20 '26 03:04

Raman


1 Answers

The error message means your post resolver


    Subscription: {
      post: () => ({
        PostSubscription: {} // This needs to return AsyncIterator
      })
    },

If I understand you correctly, you want to subscribe a postAdded, postDeleted, postUpdated, all three under the post. I understand you want to try to namespace them under the same model, which help organization better. But it has some issues, I will explain them later.

One sentence advice: it's better to have these 3 fields directly under the root subscription field.

Not to say you cannot do it, but if you really want, Suppose you are doing a subscription


    Subscription{
      post{
         postAdded: Post
         postDeleted: Post
         postUpdated(id:Int!): Post
      }
    }

Then the three nested fields are all "sharing" the same channel.

Then there's a couple of things you need to do

  1. The subscription function for post returns async iterator, not the postAdd, but the post field.

    Subscription: {
        post: {
            subscription: () =>  pubSub.asyncIterator(['postChannel'])
        }
    }

  1. Then in the mutation function, the post mutations(add, update, delete), you will need to figure out what to send to client.

something like this


    Mutation{
       createPost: (_,args,context,info)=>{
          const createdObject = // do create
          pubsub.publish("postChannel", {
             post:{
                // do not do postUpdate, postDelete, because there's nothing updated, deleted
                postAdded:createdObject
             }
          })
       }
    }

This will make what you want sort of works, but this has a couple of issues. 1. Any time any update/create/delete happened, the client is notified. Potentially this will give client incorrect information. Like so If a client subscribe with

subscription{
    post{
        postAdded
    }
}

Then when some body else updated a post, the client will get a response like this

response = {
    subscription:{
        postAdded:null
    }
}

This is probably ok to ignore null for postAdd. But it will definitely be a problem for postUpdate. Imagine a user subscribe to

subscribe{
    post{
        postUpdate(id:1)
    }
}

Then somebody added post, the client will always get notified, because the three events shared the same channel. Then he will receive

response = {
    subscription:{
        postUpdated:null
    }
}

Then, if you are using apollo client, it will remove the post:1 from the cache because it will think that post is null.

Because of these issues, It is highly recommended create many channels, preferably three channels per model. And create three root level subscriptions instead of nesting them.

For a minimum working subscription, I would redirect you to a git-repo that I made to demonstrate subscription https://github.com/hansololai/apollo_subscription_boilerplate

like image 134
Evilsanta Avatar answered Apr 23 '26 10:04

Evilsanta