Overview
Our mutation is responsible for publishing events; it's our subscription resolver's job to subscribe to them!
In this lesson, we will:
- Write our subscription resolver 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.
import { Resolvers } from "../__generated__/resolvers-types";export const Subscription: Resolvers = {Subscription: {// TODO},};
We'll also need to include this resolver in our full resolvers
object. Jump over to src/resolvers/index.ts
and uncomment a couple of lines involving the Subscription
resolver.
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 resolver object. As you might expect, we'll provide a key that matches our Subscription
type's field: listenForMessageInConversation
.
Subscription: {listenForMessageInConversation: // TODO}
Unlike our other resolvers, 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 resolver function, which has access to the same resolver arguments we're accustomed to using (parent
, args
, contextValue
, and info
.)
// ... other resolversSubscription: {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: subscriptions 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 subscription! 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 subscription 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
resolver by destructuring its third positional arguemnt. Then we'll call the asyncIterator
method.
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 mutation—called "NEW_MESSAGE_SENT"
.
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.
listenForMessageInConversation: {// @ts-ignoresubscribe: (_, __, { pubsub }) => {return pubsub.asyncIterator(["NEW_MESSAGE_SENT"])}},
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 mutation and subscription resolvers and look at them side-by-side.
Mutation: {sendMessage: () => {// ... resolver logicawait 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 subscription operation 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 mutation resolver, 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 mutation. We'll tackle that in the next lesson.
Practice
Key takeaways
- Subscription resolvers can be defined by providing an object with a
subscribe
key. Thesubscribe
function has access to all of the usual resolver arguments (parent
,args
,contextValue
, andinfo
). - An important requirement for any
subscribe
function is that it should return anAsyncIterator
type.
Up next
Nearly there! Let's wrap up our publish
call and pass along the actual data we want our subscription to return.
Share your questions and comments about this lesson
This course is currently in
You'll need a GitHub account to post below. Don't have one? Post in our Odyssey forum instead.