Overview
Let's update our subgraph setup so that we can subscribe to data.
Introducing PubSub
To both publish and subscribe to events, we'll use a package called graphql-subscriptions. This package gives us an implementation of the publish-subscribe messaging pattern called PubSub
. We can create a new instance of this class to start emitting and listening to events, such as changes to our data.
Install and use graphql-subscriptions
In a terminal opened to the root of your messages
subgraph, install the graphql-subscriptions
package.
npm install graphql-subscriptions@2.0.0
Now jump into the datasources/context.ts
file. This is where we set the context on our server, which comes through to each resolver on its third positional argument, contextValue
. We'll set it here so that all of our resolvers have access to the same PubSub
instance.
At the top of the file, import PubSub
from graphql-subscriptions
. Below our imports, we'll instantiate PubSub
in a constant called pubsub
.
import { PubSub } from "graphql-subscriptions";const pubsub = new PubSub();
Note: Using PubSub
like this automatically restricts the publishing-subscribing system to a single server instance. In production, you'll want this event system to be shared by all server instances (rather than merely in-memory). Check out this page in the official Apollo documentation for a list of PubSub
libraries approved for production.
To make pubsub
accessible to all of our resolvers, we need to include it in our already-defined createContext
function. We'll add it as a property to the object we return from this function.
export const createContext = async ({req,}: StandaloneServerContextFunctionArgument) => {const token = req.headers.authorization || "";const userId = token.split(" ")[1];return {userId,pubsub,dataSources: {db: new PrismaDbClient(),messagesAPI: new MessagesAPI(),},};};
Because we're using TypeScript, we need to make a similar update to our types/DataSourceContext.ts
file.
import { PrismaDbClient } from "../datasources/prisma/client";import { MessagesAPI } from "../datasources/messages";import { PubSub } from "graphql-subscriptions";// This interface is used with graphql-codegen to generate types for resolvers contextexport interface DataSourceContext {userId: string;pubsub: PubSub;dataSources: {db: PrismaDbClient;messagesAPI: MessagesAPI;};}
Defining the Mutation
resolver
Now that pubsub
is available to our resolvers, we can jump to our Mutation
resolvers and add the logic that triggers a new event.
Open up resolvers/Mutation.ts
. We'll find the majority of the function is here - it's just waiting for us to uncomment it! This accepts the message we send and adds it to the database, but it's not yet triggering an event we can map to our subscription.
sendMessage: async (_, { message }, { dataSources, userId }) => {const { conversationId, text } = message;const {id,text: messageText,sentFrom,sentTo,sentTime,...messageAttributes} = await dataSources.db.sendMessageToConversation({conversationId,text,userId,});// Return all of the message that was createdreturn {id,text: messageText,sentFrom,sentTo,sentTime,...messageAttributes,};};
Note: If you see some type errors showing up, try re-running your subgraph with npm run dev
. If you're using VSCode, you can also open up the command palette with Command + P, type > Typescript: Restart TS Server
, and hit enter.
Now we can include pubsub
in our destructuring of the resolver's third positional argment, contextValue
.
sendMessage: async (_, { message }, { dataSources, pubsub, userId }) => {// ... mutation};
We want to trigger our event once our message has been saved to the database successfully. We'll make some space just before returning the message from our function, and await calling the pubsub.publish
method, passing in an event string of "NEW_MESSAGE_SENT"
.
// TODO: pass along conversation ID and only relevant attributes// Issue new message event for subscriptionawait pubsub.publish("NEW_MESSAGE_SENT");
This sends a message of type "NEW_MESSAGE_SENT"
for anything that's listening for the same type. Nothing's listening for it yet!
Plus, now we have an error: we're not passing a payload to our pubsub.publish
call. For now, we'll update it to accept an empty object.
await pubsub.publish("NEW_MESSAGE_SENT", {});
Send some messages
At this point, we should be able to send some messages to a given conversation. (Make sure both your router and the messages
subgraph are running!) Jump over to Studio, and let's run a mutation to send a message to conversation "xeno-ripley-chat"
. (For this make sure that your Authorization
header references either xeno
or ripley
!)
Access the SendMessageToConversation
operation we saved in our Operation Collection.
mutation SendMessageToConversation($message: NewMessageInput!) {sendMessage(message: $message) {idtextsentTo {idname}}}
And in the Variables panel:
{"message": {"text": "Are you interested in booking another stay aboard?","conversationId": "xeno-ripley-chat"}}
And make sure that your Headers also contain the following Authorization
header. (As it's a chat between "xeno"
and "ripley"
, you can alternate which ID you reference below.)
Authorization: Bearer xeno
Now you can try querying the conversations you're a part of. The following query (GetConversations
in your Operation Collection) will show you created conversations along with their messages. Everything working? Let's move on!
query GetConversations {conversations {messages {textsentFrom {id}}}}
Subscription configuration
We're going to take care of our subscription resolver next, but we need to make one tweak in Explorer so it understands how to handle our incoming subscription operations.
Click the gear icon at the top of the Documentation panel to open up settings. Then select the Edit button next to the Connection Settings header.
This opens up a modal where we can modify where our operations should be sent. In the Subscriptions text box, we'll add the IP address and port the router's listening on: http://127.0.0.1:4000
. We'll also modify the dropdown box for Implementation and select the http-multipart
option.
The Subscription
resolver
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.