In this tutorial, we'll cover the basics of GraphQL subscriptions. GraphQL subscriptions are a way to push data from the server to the clients whenever data changes. They can be particularly useful in real-time applications, such as live chat, collaboration tools, and monitoring dashboards.
By the end of this tutorial, you'll be able to:
Prerequisites:
GraphQL subscriptions are a type of operation, like queries and mutations. However, unlike queries and mutations, subscriptions maintain a persistent connection between the client and server. This allows the server to push updates to the client whenever specific events happen.
To set up a GraphQL subscription, we need a GraphQL server that supports subscriptions (like Apollo Server), and a client that can maintain a persistent connection to the server.
// server.js
const { ApolloServer, gql, PubSub } = require('apollo-server');
const pubsub = new PubSub();
// define schema
const typeDefs = gql`
type Subscription {
messageAdded: String
}
`;
// define resolvers
const resolvers = {
Subscription: {
messageAdded: {
subscribe: () => pubsub.asyncIterator(['MESSAGE_ADDED']),
},
},
};
// create server
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
In this example, we've defined a subscription called messageAdded
. The subscribe
function returns an async iterator that listens for MESSAGE_ADDED
events.
When something happens on the server (like a new message being added), we can publish an event.
// server.js
// ...
let messageCount = 0;
const resolvers = {
Mutation: {
addMessage: (_, { content }) => {
messageCount++;
const message = `Message ${messageCount}: ${content}`;
pubsub.publish('MESSAGE_ADDED', { messageAdded: message });
return message;
},
},
// ...
};
// ...
In this example, we're publishing a MESSAGE_ADDED
event whenever a message is added. The event payload is the new message.
On the client side, we can subscribe to the messageAdded
subscription to receive updates.
// client.js
const { ApolloClient, InMemoryCache, HttpLink, split } = require('@apollo/client');
const { WebSocketLink } = require('@apollo/client/link/ws');
const { getMainDefinition } = require('@apollo/client/utilities');
const gql = require('graphql-tag');
// create Http and WebSocket links
const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
const wsLink = new WebSocketLink({ uri: 'ws://localhost:4000/graphql', options: { reconnect: true } });
// split links based on operation type
const link = split(
({ query }) => {
const definition = getMainDefinition(query);
return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
},
wsLink,
httpLink,
);
// create client
const client = new ApolloClient({ link, cache: new InMemoryCache() });
// subscribe to messages
const MESSAGE_ADDED_SUBSCRIPTION = gql`
subscription {
messageAdded
}
`;
client.subscribe({ query: MESSAGE_ADDED_SUBSCRIPTION }).subscribe({
next({ data }) {
console.log(`Received message: ${data.messageAdded}`);
},
});
In this example, we're using Apollo Client to subscribe to the messageAdded
subscription. Whenever a new message is added, it will be logged to the console.
In this tutorial, we discussed GraphQL subscriptions, which allow us to push updates from the server to the client in real-time. We also walked through a basic example of setting up a GraphQL subscription on the server and subscribing to it from the client.
To further your understanding, you might want to explore how to handle errors and reconnect attempts in GraphQL subscriptions, as well as how to use different transport protocols (like MQTT or SSE) for subscriptions.
addMessage
mutation to take a username
argument, and include the username in the published message.Solution:
const resolvers = {
Mutation: {
addMessage: (_, { username, content }) => {
messageCount++;
const message = `Message ${messageCount} from ${username}: ${content}`;
pubsub.publish('MESSAGE_ADDED', { messageAdded: message });
return message;
},
},
// ...
};
In this modified addMessage
mutation, we're taking a username
argument and including it in the message.
messageCount
subscription that publishes the current message count whenever a new message is added.Solution:
const typeDefs = gql`
// ...
type Subscription {
messageAdded: String
messageCount: Int
}
`;
const resolvers = {
// ...
Subscription: {
messageAdded: {
// ...
},
messageCount: {
subscribe: () => pubsub.asyncIterator(['MESSAGE_COUNT_UPDATED']),
},
},
};
// ...
pubsub.publish('MESSAGE_COUNT_UPDATED', { messageCount: messageCount });
// ...
In this messageCount
subscription, we're publishing a MESSAGE_COUNT_UPDATED
event with the current message count whenever a new message is added.
messageCount
subscription and log the message count to the console.Solution:
const MESSAGE_COUNT_SUBSCRIPTION = gql`
subscription {
messageCount
}
`;
client.subscribe({ query: MESSAGE_COUNT_SUBSCRIPTION }).subscribe({
next({ data }) {
console.log(`Message count: ${data.messageCount}`);
},
});
In this modified client, we're subscribing to the messageCount
subscription and logging the message count to the console.