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. Paste the following code into your file.
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: messageInConversation
.
Subscription: {messageInConversation: // 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: {messageInConversation: {subscribe: () => {},}}
Note: Our messageInConversation
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.asyncIterableIterator
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 asyncIterableIterator
method.
messageInConversation: {subscribe: (_, __, { pubsub }) => {return pubsub.asyncIterableIterator();},}
Inside the asyncIterableIterator
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.asyncIterableIterator(["NEW_MESSAGE_SENT"])},
Great! Our subscribe
function is returning a PubSubAsyncIterableIterator
type (a type that satisfies the required AsyncIterator
interface), 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: {messageInConversation: {subscribe: (_, __, { pubsub }) => {return pubsub.asyncIterableIterator(["NEW_MESSAGE_SENT"])},}}
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 (or a type that satisfies theAsyncIterator
interface, such asPubSubAsyncIterableIterator
.) - The
PubSub
class gives us a method calledasyncIterableIterator
, which lets us iterate over asynchronous events (such as sending a new message) as they're dispatched in our system.
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.