6. The Subscription resolver
15m

Overview

Our is responsible for publishing events; it's our 's job to subscribe to them!

In this lesson, we will:

  • Write our and discuss its requirements

The Subscription resolver

In the messages/src/resolvers folder, create a new file called Subscription.ts.

📂 resolvers
┣ 📄 Conversation.ts
┣ 📄 index.ts
┣ 📄 Message.ts
┣ 📄 Mutation.ts
┣ 📄 Subscription.ts
┣ 📄 Query.ts
┗ 📄 User.ts

We'll start by adding some boilerplate.

src/resolvers/Subscription.ts
import { Resolvers } from "../__generated__/resolvers-types";
export const Subscription: Resolvers = {
Subscription: {
// TODO
},
};

We'll also need to include this in our full resolvers object. Jump over to src/resolvers/index.ts and uncomment a couple of lines involving the Subscription .

src/resolvers/index.ts
import { Query } from "./Query";
import { Mutation } from "./Mutation";
import { Message } from "./Message";
import { Conversation } from "./Conversation";
import { User } from "./User";
import { Subscription } from "./Subscription";
const resolvers = {
...Query,
...Mutation,
...Conversation,
...Message,
...User,
...Subscription,
};
export default resolvers;

Back in Subscription.ts, we'll finish up our object. As you might expect, we'll provide a key that matches our Subscription type's : listenForMessageInConversation.

src/resolvers/Subscription.ts
Subscription: {
listenForMessageInConversation: // TODO
}

Unlike our other , however, we won't define a function as this key's value. Instead, we'll open up another object—and define a subscribe key inside of it. The subscribe key is where we'll put our actual function, which has access to the same resolver we're accustomed to using (parent, args, contextValue, and info.)

src/resolvers/Subscription.ts
// ... other resolvers
Subscription: {
listenForMessageInConversation: {
subscribe: () => {},
}
}

Note: Our listenForMessageInConversation object can define an additional property: resolve. We can use the resolve function to further drill into the payload that we receive from each emitted event. We won't use resolve in this course.

Great! Now what should this subscribe function return? Consulting our schema, we're expected to return a Message type. However, there's one catch: deal with asynchronous data. The most important requirement for the subscribe function is that it must return an AsyncIterator type.

AsyncIterator

AsyncIterator is an interface that allows us to iterate over asynchronous results—exactly the kind of data we'd expect to get from a ! We won't have to define a new class to make this work; the PubSub library has us covered with a special method that handles all the details.

Note: Want to learn more about the AsyncIterator interface? Check out the MDN Web Docs.

The pubsub.asyncIterator method accepts an array, where we can specify the events that the should be listening for, and whose results it should iterate over.

Let's set this up! Back in Subscription.ts, we'll start by accessing pubsub inside of the subscribe by destructuring its third positional arguemnt. Then we'll call the asyncIterator method.

src/resolvers/Subscription.ts
listenForMessageInConversation: {
subscribe: (_, __, { pubsub }) => {
return pubsub.asyncIterator();
},
}

Inside the asyncIterator method, we'll define the array of events we want to listen for. We have just a single event—the one we published in our —called "NEW_MESSAGE_SENT".

src/resolvers/Subscription.ts
subscribe: (_, __, { pubsub }) => {
return pubsub.asyncIterator(["NEW_MESSAGE_SENT"])
},

At this point, you'll probably see an error from TypeScript about the type that the subscribe function returns. This is a known bug which concerns mismatched types between libraries. Currently, there are two workarounds. You can either apply // @ts-ignore to the line just above the error, or you can modify the object returned as shown below under Option 2. Please pick whichever option you like best! From here on out, we'll use Option 2 in our examples.

Option 1
listenForMessageInConversation: {
// @ts-ignore
subscribe: (_, __, { pubsub }) => {
return pubsub.asyncIterator(["NEW_MESSAGE_SENT"])
}
},
Option 2
listenForMessageInConversation: {
subscribe: (_, __, {pubsub}) => {
return {
[Symbol.asyncIterator]: () => pubsub.asyncIterator(["NEW_MESSAGE_SENT"])
}
}
},

Great! Our subscribe function is returning an AsyncIterator type, and we've configured it for our specific "NEW_MESSAGE_SENT" event. But...how does this subscribe function actually return data—specifically, each new message sent to a conversation?

Publishing an event with payload

Let's take our and and look at them side-by-side.

Mutation: {
sendMessage: () => {
// ... resolver logic
await pubsub.publish("NEW_MESSAGE_SENT", {}); 1️⃣
// ... return message
},
},
Subscription: {
listenForMessageInConversation: {
subscribe: (_, __, { pubsub }) => {
return {
[Symbol.asyncIterator]: () => pubsub.asyncIterator(["NEW_MESSAGE_SENT"]) 2️⃣
}
},
}
}

Here we can see 1) where an event is published, and 2) where the event is being subscribed to.

But is there actually any message data getting passed along with it? In other words—will our actually return anything remotely like the Message type in our schema?

Well...not yet. There's an important piece that we're missing here, and that's the event payload. Inside our , we passed an empty object ({}) as the payload to our publish call. So anytime a "NEW_MESSAGE_SENT" event is published, an empty object is all that gets passed along as the actual event data!

What we need to do instead is pass along the message that was submitted as part of the . We'll tackle that in the next lesson.

Practice

Which of the following are differences between subscription resolvers and other resolvers?

Key takeaways

  • can be defined by providing an object with a subscribe key. The subscribe function has access to all of the usual (parent, args, contextValue, and info).
  • An important requirement for any subscribe function is that it should return an AsyncIterator type.

Up next

Nearly there! Let's wrap up our publish call and pass along the actual data we want our to return.

Previous

Share your questions and comments about this lesson

This course is currently in

beta
. Your feedback helps us improve! If you're stuck or confused, let us know and we'll help you out. All comments are public and must follow the Apollo Code of Conduct. Note that comments that have been resolved or addressed may be removed.

You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.