Previous page: Create an Angular component to send a message to the GraphQL API
GraphQL Subscriptions
Create a GraphQL Subscription to send real-time message notifications
We can retrieve a list of messages and create messages with our GraphQL API from our Angular app. Now we need a way of subscribing and instantly notifying all apps of when a message gets sent using a subscription.
We will use GraphQL Subscriptions to achieve this, which use websockets under the hood.
Add the subscription to our schema
We're going to add a Subscription
to our schema to describe events we're
interested in such as when a new message has been added.
Within your project folder, open the file web-api/index.js
and add these new
lines within middle of the document to the typDefs
constant:
// Existing code...
const typeDefs = gql`
# Existing code...
type Mutation {
# Existing code...
}
type Subscription { # <-- Add new subscription here
messageAdded: Message
}
${messageGQL}
${userGQL}
`;
// Existing code...
Next we need to create a context function that will resolve data related to
our websocket connection context such as user data and data sources that can
be used in our subscription resolvers. We need to configure our ApolloServer
and add the following code after the playground
property:
// Existing code...
const server = new ApolloServer({
// Existing code...
playground: {
// Existing code...
},
context: ({ req, connection }) => { // <-- Add new method
if(connection) {
return {
...connection.context,
dataSources
};
}
}
});
This will allow our resolvers to work with existing data source instances.
We also need to configure the server instance to setup handling of
subscriptions. Add the following line after the new ApolloServer
.
// Existing code...
const server = new ApolloServer({
// Existing code...
});
server.installSubscriptionHandlers(httpServer); // <-- Add new code
// Existing code...
Connect to Slack's real-time-messaging API (RTM)
Before we can create our subscription, we first need to make a connection to Slack's real-time messaging API. We will use the Node Slack SDK again for this.
Open the file web-api/slack/connection.js
, then add the new RTMClient
from
the @slack/client
module and PubSub
dependency from apollo-angular
to
create a new instance.
const { WebClient, RTMClient /* <-- Add new import */ } = require('@slack/client');
const { PubSub } = require('apollo-server'); // <-- Add new import
let webAPI, rtmAPI; // <-- Define new variable
const pubsub = new PubSub(); // <-- Create new instance
// Existing code...
The PubSub
module will give us a shared instance of an event generator. This
will allow us to publish events to any subscribed clients any time an event
of interest happens.
When Slack receives a message it will notify our GraphQL API through the use
of the RTMClient
instance, we will then need to notify all Angular apps
connected to our API. This is done by publishing an event through the
PubSub
instance. GraphQL will use the PubSub
instance to publish the
schema event messageAdded
with the new message
data.
Next we need to redefine the connect
function to create a memoized
connection to Slack's real-time messaging API that we can reuse in our new
subscriptions.
// Existing code...
function connect({ webToken, rtmToken }) { // <-- Destructure new token
console.log('webToken', webToken);
console.log('rtmToken', rtmToken);
webAPI = new WebClient(webToken);
rtmAPI = new RTMClient(rtmToken); // <-- Create new RTMClient instance with new token
rtmAPI.start(); // <-- Boot RTMClient
}
function getConnection() {
// Existing code...
}
// Existing code...
In order to retrieve a reference to our new connection with Slack from our
subscriptions, we need to redefine the getConnection
function to return the
new RTMClient
instance and also export the new shared PubSub instance.
// Existing code...
function connect({ webToken, rtmToken }) {
// Existing code...
}
function getConnection() {
if(!webAPI || !rtmAPI /* <-- Check connection exists */) {
throw Error('Slack connections not found');
}
return { webAPI, rtmAPI /* <-- Return new reference */ };
}
module.exports = { connect, getConnection, pubsub };
We can now listen to new Slack notifications and publish new events of interest such as new sent messages within our subscriptions.
Create the Subscription resolver for Slack real-time messaging
Create and open the file
web-api/api/slack-integration/resolvers/messages.messageAdded.subscription.js
.
touch web-api/api/slack-integration/resolvers/messages.messageAdded.subscription.js
First we will need to import a few modules at the top of the file and define a couple of constants that will describe the connection.
const { pubsub, getConnection } = require('../../../slack/connection');
const slackMessageMap = require('../data-mappings/message');
const { rtmAPI: rtm } = getConnection();
const TOPIC = 'messageAdded';
Most of the imports should be familiar. The slackMessageMap
we've used a
couple of times previously. This will again be needed to transform Slack message
data into a compatible model for when our schema publishes the data for a
messageAdded
event.
We get the real-time connection started with Slack (rtm
) and define the
TOPIC
of messageAdded
, which we declared in our schema to describe new
events we're interested in.
We need to define an event handler that will handle the Slack response for
when a new message is created. Then control when this method gets called by
setting up a listener with the RTMClient on
method.
// Existing code...
function onMessageHandler(messageData) { // <-- Add new handler function
pubsub.publish(TOPIC, {
[TOPIC]: slackMessageMap(messageData)
});
}
rtm.on('message', onMessageHandler); // <-- Listen to Slack message events
The onMessageHandler
function will get called when the Slack client emits a
new message
from the API. This will then call publish
(in the handler) on
the pubsub
instance to publish a new messageAdded
event.
For the second parameter to pubsub.publish
, we define a payload object to hold
message data with each new publication. This will map the message data sent
from Slack to our schema compatible message model
[TOPIC]: slackMessageMap(messageData)
.
After the handler, add the pubsub async iterator function to our module exports.
// Existing code...
module.exports = {
subscribe: () => pubsub.asyncIterator(TOPIC)
};
The properties on the export function will plug in to our schema and allow
our Angular app to connect to the asyncIterator
over a websocket connection
for real-time notifications. We just need to add our subscription resolver to
our resolver export bundle.
Open the file web-api/api/slack-integration/resolvers/messages.resolvers.js
and add the following lines:
// Existing code...
const messageAdded = require('./messages.messageAdded.subscription'); // <-- Add the new module
module.exports = {
// Existing code...
Mutation: {
// Existing code...
},
Subscription: { // <-- Add the new subscription resolver
messageAdded
},
// Existing code...
};
We now have everything we need for our Angular app to connect to our GraphQL subscription.
Next, lets connect to our GraphQL Subscription for live message updates
Previous page: Create an Angular component to send a message to the GraphQL API