25 Jul 2022
GraphQL Subscriptions lay the foundations to subscribe to data changes. Subscriptions are great for knowing what, and why data changed. If you're working with real-time data such as a chat application or notification then GraphQL Subscriptions will get you quite far.
GraphQL Subscriptions are often used via WebSockets, but we'll leave that for another lesson. Today we'll explore using Server Sent Events which comes as the default transport mechanism with GraphQL Yoga.
Let's take the following GraphQL schema:
type Query {
room(id: ID!): [Message!]!
}
type Mutation {
send(input: SendMessageInput!): Message!
}
type Subscription {
newMessage(roomId: ID!): Message!
}
type Message {
from: String
body: String
}
input SendMessageInput {
roomId: ID!
from: String!
body: String!
}
This schema lets you view messages from a room, and subscribe to new messages via the newMessage
subscription. To send messages we'll use the send
mutation.
Next, we'll import our schema, and instantiate a new GraphQL Yoga server in Node:
import { createServer } from "@graphql-yoga/node";
import * as fs from "fs";
import * as path from "path";
import { Resolvers } from "./types";
const typeDefs = fs.readFileSync(path.join(process.cwd(), "schema.graphql"), {
encoding: "utf-8",
});
const resolvers: Resolvers = {
Query: {
room: () => [],
},
Mutation: {
send: (_, { input }) => {
const { roomId, ...newMessage } = input;
return newMessage;
},
},
Subscription: {},
};
const server = createServer({
schema: {
typeDefs,
resolvers,
},
});
server.start();
We're also using the GraphQL Code Generator to generate types for our resolvers. We cover this in episode 26.
Right now our server won't do much. Our send
mutation will return the newMessage
from our input
argument to satisfy the GraphQL schema.
Let's begin by importing createPubSub
from @graphql-yoga/node
:
import { createPubSub } from "@graphql-yoga/node";
With this we can instantiate a new pubsub bus:
const pubSub = createPubSub();
Since we'll be watching for new messages per channel, let's use a specific topic, and provide this as a generic to createPubSub
:
const pubSub = createPubSub<{
newMessage: [payload: { from: string; body: string }];
}>();
The payload
here is of a specific type. In this case we'll match the Message
type.
Now that we have our pubSub
instantiated we can add this to our server context:
const server = createServer({
schema: {
typeDefs,
resolvers,
},
context: {
pubSub,
},
});
Next inside of our mutation resolver for send
we'll publish a new message to the topic newMessage
, passing along the roomId
, and message using positional arguments:
const resolvers: Resolvers = {
// ...
Mutation: {
send: (_, { input }, { pubSub }) => {
const { roomId, ...newMessage } = input;
pubSub.publish("newMessage", roomId, newMessage);
return newMessage;
},
},
// ...
};
You'll notice we fetch pubSub
from our server context argument.
Now when we send a message it will publish a new message to the topic with the provided roomId
.
Finally, we need to subscribe to the specific topic. Inside of our Subscription
resolver we'll define newMessage
:
const resolvers: Resolvers = {
// ...
Subscription: {
newMessage: {
// ...
},
},
};
The subscription resolver works similar to how a regular works. We have access to the same arguments you would in a query and resolver. Let's fetch roomId
from our args, and pubSub
from context for the function subscribe
:
const resolvers: Resolvers = {
// ...
Subscription: {
newMessage: {
subscribe: (_, { roomId }, { pubSub }) => {
// ...
},
},
},
};
Now all that's left to do is subscribe
to our pubSub
on the newMessage
topic with our dynamic roomId
. Let's also provide the resolve
function that simply returns our payload "as is":
const resolvers: Resolvers = {
// ...
Subscription: {
newMessage: {
subscribe: (_, { roomId }, { pubSub }) =>
pubSub.subscribe("newMessage", roomId),
resolve: (payload) => payload,
},
},
};
That's it! You should now be able to subscribe to the newMessage
topic, and send
messages from another GraphQL request.
Using GraphiQL we can subscribe to the room with ID 1
:
subscription {
newMessage(roomId: "1") {
from
body
}
}
Then inside another GraphiQL we can send a new message to the same room:
mutation {
send(input: { roomId: "1", from: "Jamie", body: "Hello Yoga!" }) {
from
body
}
}
You should see that the subscription returns the message sent in the other window.
If you change the roomId
to 2
and resend the mutation, nothing will appear in the subscription. This is because it's only subscribed to messages on the topic provided in the subscription.