Odyssey

Federated subscriptions with TypeScript & Apollo Server

Course overview and setupFederated subscriptionsSetting up subscriptionsSubscriptions over HTTP callbacksPubSub and resolversThe Subscription resolverReturning subscription dataUsing withFilterSynchronizing graph-wide updates
7. Returning subscription data
2m

Overview

We're missing the important message data our subscription is listening for. In this lesson, we will:

  • Update our mutation resolver to pass along the payload data
  • See our subscription in action

Passing along the Message

Currently our mutation resolver is passing along an empty object ({}) as the payload of each "NEW_MESSAGE_SENT" event it publishes. Let's fix that.

Open up the resolvers/Mutation.ts file. Inside the sendMessage resolver, find the pubsub.publish function and make some space in that second argument: the empty object.

resolvers/Mutation.ts
await pubsub.publish("NEW_MESSAGE_SENT", {
// TODO - pass along the actual submitted message!
});

To convey data directly to our subscription resolver, we'll first specify the name of that Subscription field: messageInConversation. This is an object which will take some additional properties.

await pubsub.publish("NEW_MESSAGE_SENT", {
messageInConversation: {},
});

The properties inside this object will be exactly what our Subscription.messageInConversation resolver will return for each new event. This is where the return type we defined in our schema comes in: we need to make sure that we pass along an object that matches the shape of our schema's Message type.

Taking another look at the Message type, we'll see exactly the properties we need to include. (We're omitting the field descriptions here for easier readability.)

type Message {
id: ID!
text: String!
sentFrom: User!
sentTo: User!
sentTime: String
}

Good news, we already have access to these properties in the sendMessage resolver! At the top of the function, we can see that these properties are available and have already been destructured from the dataSources.db.sendMessageToConversation call.

Let's pass them in now to our object.

await pubsub.publish("NEW_MESSAGE_SENT", {
messageInConversation: {
id,
text: messageText,
sentFrom,
sentTo,
sentTime,
},
});

Note that we're renaming the messageText field to simply text, to match the field name in the schema.

import { Resolvers } from "../__generated__/resolvers-types";
export const Mutation: Resolvers = {
Mutation: {
createConversation: async (_, { recipientId }, { dataSources, userId }) => {
return dataSources.db.createNewConversation({ userId, recipientId })
},
sendMessage: async (_, { message }, { dataSources, pubsub, userId }) => {
const { conversationId, text } = message;
const {
id,
text: messageText,
sentFrom,
sentTo,
sentTime,
...messageAttributes
} = await dataSources.db.sendMessageToConversation({
conversationId,
text,
userId,
});
await pubsub.publish("NEW_MESSAGE_SENT", {
messageInConversation: {
id,
text: messageText,
sentFrom,
sentTo,
sentTime,
},
});
// Return all of the message that was created
return {
id,
text: messageText,
sentFrom,
sentTo,
sentTime,
...messageAttributes,
};
}
}
}
import { Resolvers } from "../__generated__/resolvers-types";
export const Subscription: Resolvers = {
Subscription: {
messageInConversation: {
subscribe: (_, __, { pubsub }) => {
return pubsub.asyncIterableIterator(["NEW_MESSAGE_SENT"])
},
},
},
}

Testing the subscription

It's time to put our subscription to the test! First, make sure that both subgraphs and the rover dev process are still running. Then jump over to http://localhost:4000.

If you saved your subscription operation at the start of the course, now's the time to access it in from your Operation Collection! Otherwise, copy and paste the operation below into the Explorer's Operation field.

subscription SubscribeToMessagesInConversation($messageInConversationId: ID!) {
messageInConversation(id: $messageInConversationId) {
text
sentTime
}
}

And in the Variables panel:

Variables
{
"messageInConversationId": "wardy-eves-chat"
}

And in the Headers panel:

Headers
Authorization: Bearer eves

Submit the operation. At the bottom of the Response panel a new window labeled Subscriptions appears: we should see a green status indicator, along with a message that the router is listening for new events.

http://localhost:4000

A screenshot of Sandbox, showing a subscription running successfully

Next, let's open up a new operation tab. This time, we'll access our saved SendMessageToConversation operation.

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

Add the following to the Variables panel:

Variables
{
"message": {
"conversationId": "wardy-eves-chat",
"text": "I'm offering a discount of up to 60%!"
}
}

Finally, make sure that you're still passing along the correct values for your Authorization header!

Headers
Authorization: Bearer eves

Submit the mutation and... data! We should see both the response from the mutation, as well as the new event in our Subscriptions panel! Our subscription is working!

http://localhost:4000

A screenshot of Sandbox, showing both mutation and subscription data in the Response panel

Practice

Subscribing to data
Whenever our sendMessage mutation is called, we use the server's PubSub instance to call the 
 
 method. This method accepts two arguments: the 
 
 and the 
 
. Inside our subscribe function, we use the server's PubSub instance to call the 
 
 method. This method accepts an 
 
 of event names whose payload should be iterated over and returned.

Drag items from this box to the blanks above

  • asyncIterator

  • trigger

  • payload

  • event name

  • subscribe

  • destination

  • object

  • array

  • metadata

  • publish

To pick up a draggable item, press the space bar. While dragging, use the arrow keys to move the item. Press space again to drop the item in its new position, or press escape to cancel.

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
}
Code Challenge!

Update the resolver for Mutation.changeMissionStatus to publish an event called STATUS_UPDATE. Include the entire mission object.

Key takeaways

  • We can use the PubSub method publish to pass along the data that should arrive in subscribe functions listening for the same event. This data should match the return type we specified for our subscription field in the schema.

Up next

Our subscription is working! But we have some opportunities for optimization. Let's dive into those in the next lesson.

Previous
Next

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.

              subscription

              A long-lived, real-time GraphQL operation that enables real-time communication by allowing clients to receive data updates from the server when specific events or changes occur.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              subscription

              A long-lived, real-time GraphQL operation that enables real-time communication by allowing clients to receive data updates from the server when specific events or changes occur.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              argument

              A key-value pair associated with a particular schema field that lets operations pass data to that field's resolver.

              Argument values can be hardcoded as literal values (shown below for clarity) or provided via GraphQL variables (recommended).

              query GetHuman {
              human(id: "200") {
              name
              height(unit: "meters")
              }
              }
              subscription

              A long-lived, real-time GraphQL operation that enables real-time communication by allowing clients to receive data updates from the server when specific events or changes occur.

              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              resolver

              A function that populates data for a particular field in a GraphQL schema. For example:

              const resolvers = {
              Query: {
              author(root, args, context, info) {
              return find(authors, { id: args.id });
              },
              },
              };
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              subscription

              A long-lived, real-time GraphQL operation that enables real-time communication by allowing clients to receive data updates from the server when specific events or changes occur.

              subgraphs

              A service in a federated GraphQL architecture. Acts as a module for a supergraph. Includes both GraphQL services and REST services integrated via Apollo Connectors.

              subscription

              A long-lived, real-time GraphQL operation that enables real-time communication by allowing clients to receive data updates from the server when specific events or changes occur.

              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              router

              The single access point for a federated GraphQL architecture. It receives incoming operations and intelligently routes them across component services before returning a unified response.

              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              operation

              A single query, mutation, or subscription that clients send to a GraphQL server to request or manipulate data.

              mutation

              A GraphQL operation that modifies data on the server. It allows clients to perform create, update, or delete operations, altering the underlying data.

              subscription

              A long-lived, real-time GraphQL operation that enables real-time communication by allowing clients to receive data updates from the server when specific events or changes occur.

              subscription

              A long-lived, real-time GraphQL operation that enables real-time communication by allowing clients to receive data updates from the server when specific events or changes occur.

              field

              A unit of data that belongs to a type in a schema. Every GraphQL query requests one or more fields.

              type Author {
              # id, firstName, and lastName are all fields of the Author type
              id: Int!
              firstName: String
              lastName: String
              }
              subscription

              A long-lived, real-time GraphQL operation that enables real-time communication by allowing clients to receive data updates from the server when specific events or changes occur.

              NEW COURSE ALERT

              Introducing Apollo Connectors

              Connectors are the new and easy way to get started with GraphQL, using existing REST APIs.

              Say goodbye to GraphQL servers and resolvers—now, everything happens in the schema!

              Take the course