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:

  1. Queries: Retrieve data from the server.
  2. Mutations: Modify data on the server.
  3. 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.

How do I handle authentication and authorization in GraphQL subscriptions?

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.

0/10000

No comments so far