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
Refer to the following schema to complete the code challenge below.
type Subscription {"Subscribe to changes in a mission status"newMissionStatus(id: ID!): Mission}type Mutation {"Update the status for a particular mission"changeMissionStatus(missionId: ID!, status: String!): Mission}
Implement the resolver function for Subscription.newMissionStatus. The resolver listens to the STATUS_UPDATE event.
Key takeaways
- Subscription resolvers can be defined by providing an object with a
subscribekey. Thesubscribefunction has access to all of the usual resolver arguments (parent,args,contextValue, andinfo). - An important requirement for any
subscribefunction is that it should return anAsyncIteratortype (or a type that satisfies theAsyncIteratorinterface, such asPubSubAsyncIterableIterator.) - The
PubSubclass 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
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.