Implementing Real-time Subscriptions in GraphQL
Real-time subscriptions are an integral part of modern web applications, allowing users to receive updates as they occur without needing to refresh the page. GraphQL, an increasingly popular query language for APIs, provides powerful tools for implementing these real-time subscriptions. In this blog post, we will guide you through the process of implementing real-time subscriptions in GraphQL. We will explore key concepts, illustrate how to set up a GraphQL server with subscription support, and provide code examples for both the server and the client. Finally, we'll address some frequently asked questions related to GraphQL subscriptions.
Understanding GraphQL Subscriptions
Before diving into implementation details, let's take a moment to understand what GraphQL subscriptions are and how they differ from other GraphQL operations.
GraphQL supports three primary operation types:
- Queries: Retrieve data from the server.
- Mutations: Modify data on the server.
- Subscriptions: Receive real-time updates from the server.
Subscriptions enable real-time updates by maintaining a long-lived connection between the client and server. When an event occurs on the server, such as data being added or modified, the server pushes updates to the subscribed clients.
Setting Up a GraphQL Server with Subscription Support
To get started with implementing real-time subscriptions, we first need to set up a GraphQL server with subscription support. For this example, we'll use Apollo Server, a popular GraphQL server implementation.
Installing Dependencies
Create a new Node.js project and install the necessary dependencies:
npm init -y npm install apollo-server graphql
Defining the Schema
Create a new file called schema.js
and define your GraphQL schema. For this example, we'll create a simple schema with a Message
type and a messages
query.
const { gql } = require("apollo-server"); const typeDefs = gql` type Message { id: ID! content: String! createdAt: String! } type Query { messages: [Message!] } `; module.exports = typeDefs;
Implementing Resolvers
Next, create a new file called resolvers.js
and define the resolvers for your schema. For simplicity, we'll use an in-memory array to store messages.
let messages = []; const resolvers = { Query: { messages: () => messages, }, }; module.exports = resolvers;
Setting Up the Apollo Server
Create a new file called index.js
and set up the Apollo Server.
const { ApolloServer } = require("apollo-server"); const typeDefs = require("./schema"); const resolvers = require("./resolvers"); const server = new ApolloServer({ typeDefs, resolvers }); server.listen().then(({ url }) => { console.log(`🚀 Server ready at ${url}`); });
Now, you should have a basic GraphQL server running. Let's move on to adding subscription support.
Implementing Subscriptions on the Server
To implement subscriptions, we need to extend our schema with a Subscription
type and update our Apollo Server configuration to support subscriptions.
Extending the Schema
Update schema.js
to include a Subscription
type and a messageAdded
subscription field.
const { gql } = require("apollo-server"); const typeDefs = gql` type Message { id: ID! content: String! createdAt: String! } type Query { messages: [Message!] } type Subscription { messageAdded: Message! } `; module.exports = typeDefs;
Implementing Subscription Resolvers
Update resolvers.js
to include the Subscription
resolvers. We'll use the “PubSub` (short for Publish-Subscribe) utility provided by Apollo Server to handle event-based communication between our resolvers and subscriptions.
First, install the graphql-subscriptions
package:
npm install graphql-subscriptions
Then, update resolvers.js
to import PubSub
and add the Subscription
resolvers:
const { PubSub } = require("graphql-subscriptions"); const pubsub = new PubSub(); const MESSAGE_ADDED = "MESSAGE_ADDED"; let messages = []; const resolvers = { Query: { messages: () => messages, }, Subscription: { messageAdded: { subscribe: () => pubsub.asyncIterator(MESSAGE_ADDED), }, }, }; module.exports = resolvers;
Now, whenever a new message is added, the pubsub.publish
method should be called to notify the subscribed clients. Update the Mutation
resolver to add the addMessage
mutation:
const resolvers = { // ... Mutation: { addMessage: (parent, { content }) => { const message = { id: messages.length + 1, content, createdAt: new Date().toISOString(), }; messages.push(message); pubsub.publish(MESSAGE_ADDED, { messageAdded: message }); return message; }, }, // ... };
Updating the Apollo Server Configuration
Modify index.js
to include the necessary imports and update the Apollo Server configuration to support subscriptions:
const { ApolloServer, PubSub } = require("apollo-server"); const typeDefs = require("./schema"); const resolvers = require("./resolvers"); const { createServer } = require("http"); const server = new ApolloServer({ typeDefs, resolvers, subscriptions: { path: "/subscriptions", }, }); const httpServer = createServer(server.createHandler()); server.installSubscriptionHandlers(httpServer); const PORT = process.env.PORT || 4000; httpServer.listen(PORT, () => { console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`); console.log(`🚀 Subscriptions ready at ws://localhost:${PORT}${server.subscriptionsPath}`); });
Implementing Subscriptions on the Client
Now that the server supports subscriptions, let's implement the client side. We'll use Apollo Client, a popular GraphQL client library.
Installing Dependencies
Create a new frontend project and install the necessary dependencies:
npm init -y npm install @apollo/client graphql
Setting Up the Apollo Client
Create a new file called client.js
and set up the Apollo Client. For this example, we'll use WebSocket as the transport for our subscriptions.
const { ApolloClient, InMemoryCache, HttpLink, split } = require("@apollo/client"); const { WebSocketLink } = require("@apollo/client/link/ws"); const { getMainDefinition } = require("@apollo/client/utilities"); const httpLink = new HttpLink({ uri: "http://localhost:4000", }); const wsLink = new WebSocketLink({ uri: "ws://localhost:4000/subscriptions", options: { reconnect: true, }, }); const link = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === "OperationDefinition" && definition.operation === "subscription" ); }, wsLink, httpLink ); const client = new ApolloClient({ link, cache: new InMemoryCache(), }); module.exports = client;
Subscribing to New Messages
Nowthat the Apollo Client is set up, let's use it to subscribe to new messages. Create a new file called subscription.js
and import the necessary dependencies:
const { gql } = require("@apollo/client"); const client = require("./client");
Define the MESSAGE_ADDED
subscription query:
const MESSAGE_ADDED_SUBSCRIPTION = gql` subscription MessageAdded { messageAdded { id content createdAt } } `;
Now, subscribe to the messageAdded
event and log the new messages to the console:
client .subscribe({ query: MESSAGE_ADDED_SUBSCRIPTION, }) .subscribe({ next({ data: { messageAdded } }) { console.log(`New message: ${messageAdded.content}`); }, error(err) { console.error("Error:", err); }, });
Run the subscription.js
file with Node.js:
node subscription.js
When new messages are added through the addMessage
mutation, the subscribed client should now receive and log the updates in real-time.
FAQ
What transport protocols can be used for GraphQL subscriptions?
GraphQL subscriptions can be implemented using different transport protocols. The most common choice is WebSocket, as it provides a full-duplex communication channel between the client and server. Other alternatives include Server-Sent Events (SSE) and MQTT.
Authentication and authorization can be implemented using a combination of middleware and context functions in the GraphQL server. You can pass the user's authentication token through the WebSocket connection's connectionParams
and validate it in the server's onConnect
function. For authorization, you can use the context function to control access to subscription events based on the user's role or permissions.
How can I manage subscription state and client reconnections?
Apollo Client's WebSocketLink
provides a reconnect
option that, when set to true
, will attempt to automatically reconnect the WebSocket if the connection is lost. You can also manage the subscription state by implementing custom logic in the server's onConnect
, onDisconnect
, and onOperation
lifecycle methods.
Can I filter or throttle subscription events?
Yes, you can use the withFilter
function from the graphql-subscriptions
package to filter subscription events based on custom criteria. For throttling, you can implement custom logic in your resolvers or use third-party libraries like graphql-rate-limit
.
Sharing is caring
Did you like what Mehul Mohan wrote? Thank them for their work by sharing it on social media.
No comments so far
Curious about this topic? Continue your journey with these coding courses: