6. Codealong - PubSub, events, mutations
10m

Overview

Let's update our 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 , install the graphql-subscriptions package.

messages
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 on its third positional , contextValue. We'll set it here so that all of our 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.

datasources/context.ts
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 , 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 context
export interface DataSourceContext {
userId: string;
pubsub: PubSub;
dataSources: {
db: PrismaDbClient;
messagesAPI: MessagesAPI;
};
}

Defining the Mutation resolver

Now that pubsub is available to our , we can jump to our Mutation 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 .

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 created
return {
id,
text: messageText,
sentFrom,
sentTo,
sentTime,
...messageAttributes,
};
};

Note: If you see some type errors showing up, try re-running your 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 '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 subscription
await 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 and the messages are running!) Jump over to Studio, and let's run a 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 we saved in our Operation Collection.

mutation SendMessageToConversation($message: NewMessageInput!) {
sendMessage(message: $message) {
id
text
sentTo {
id
name
}
}
}

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
https://studio.apollographql.com

A screenshot of Explorer showing the response after sending a message to a particular conversation

Now you can try the conversations you're a part of. The following query (GetConversations in your Collection) will show you created conversations along with their messages. Everything working? Let's move on!

query GetConversations {
conversations {
messages {
text
sentFrom {
id
}
}
}
}

Subscription configuration

We're going to take care of our next, but we need to make one tweak in Explorer so it understands how to handle our incoming subscription .

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.

http://studio.apollographql.com

A screenshot Studio showing how to access the Settings panel

This opens up a modal where we can modify where our should be sent. In the Subscriptions text box, we'll add the IP address and port the 's listening on: http://127.0.0.1:4000. We'll also modify the dropdown box for Implementation and select the http-multipart option.

http://studio.apollographql.com

A screenshot of the modal window in Studio, highlighting where we specify the subscription IP address and port and specify the implementation

The Subscription resolver

Previous

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.