The most recognized solution for real-time problems is WebSockets (WS), where there is a persistent connection between the client and the server, and either can start sending data at any time. One of the latest implementations of WS is GraphQL subscriptions.
With GraphQL subscriptions, you can easily add real-time functionalities to your application. There is an easy and standard way to implement a subscription in the GraphQL app. The client just has to make a subscription query to the server, which specifies the event and the data shape. With this query, the client establishes a long-lived connection with the server on which it listens to specific events. Just as how GraphQL solves the over-fetching problem in the REST API, a subscription continues to extend the solution for real-time.
In this post, we will learn how to bring real-time functionality to your app by implementing GraphQL subscriptions with Pusher to manage Pub/Sub capabilities. The goal is to configure a Pusher channel and implement two subscriptions to be exposed by your GraphQL server. We will be implementing this in a Node.js runtime environment.
Why are we doing this using Pusher?
- Pusher, being a hosted real-time services provider, relieves us from managing our own real-time infrastructure, which is a highly complex problem.
- Pusher provides an easy and consistent API.
- Pusher also provides an entire set of tools to monitor and debug your realtime events.
- Events can be triggered by and consumed easily from different applications written in different frameworks.
We will start with a repository that contains a codebase for a simple GraphQL backend in Node.js, which is a minimal representation of a blog post application. The entities included are:
- Link - Represents an URL and a small description for the Link
- User - Link belongs to User
- Vote - Represents users vote for a Link
In this application, a User can sign up and add or vote a Link in the application, and other users can upvote the Link. The database schema is built using Prisma and SQLite for quick bootstrapping. In the backend, we will use graphql-yoga as the GraphQL server implementation. To test our GraphQL backend, we will use the graphql-playground by Prisma, as a client, which will perform all queries and mutations on the server.
To set up the application:
- Clone the repository here
- Install all dependencies using
- Set up a database using prisma-cli with following commands
Note: Migrations are experimental features of the Prisma ORM, but you can ignore them because you can have a different backend setup for DB interactions. The purpose of using Prisma here is to quickly set up the project and dive into subscriptions.
A new directory, named Prisma, will be created containing the schema and database in SQLite. Now, you have your database and app set up and ready to use.
To start the Node.js application, execute the command:
Navigate to http://localhost:4000 to see the graphql-playground where we will execute our queries and mutations.
Our next task is to add a GraphQL subscription to our server to allow clients to listen to the following events:
- A new Link is created
- A Link is upvoted
To add subscriptions, we will need an npm package called graphql-pusher-subscriptions to help us interact with the Pusher service from within the GraphQL resolvers. The module will trigger events and listen to events for a channel from the Pusher service.
Before that, let’s first create a channel in Pusher. To configure a Pusher channel, head to their website at Pusher to create an account. Then, go to your dashboard and create a channels application. Choose a name, the cluster closest to your location, and frontend tech as React and backend tech as Node.js.
You will receive the following code to start.
Now, we add the graphql-pusher-subscription package. This package will take the Pusher channel configuration and give you an API to trigger and listen to events published on the channel.
Now, we import the package in the src/index.js file.
After the PusherChannel class provided by the module accepts a configuration for the channel, we need to instantiate the class and create a reference Pub/Sub to the object. We give the Pusher config object given while creating the channel.
Now, we add “pubsub” to the context so that it is available to all the resolvers. The channel field tells the client which channel to subscribe to. Here we have the channel “graphql-subscription”.
The above part enables us to access the methods we need to implement our subscriptions from inside our resolvers via context.pubsub.
Subscribing to Link-created Event
The first step to add a subscription is to extend the GraphQL schema definition.
Next, we implement the resolver for the “newLink” subscription type field. It is important to note that resolvers for subscriptions are different from queries and mutations in minor ways.
1. They return an AsyncIterator instead of data, which is then used by a GraphQL server to publish the event payload to the subscriber client.
2. The subscription resolvers are provided as a value of the resolve field inside an object. The object should also contain another field named “resolve” that returns the payload data from the data emitted by AsyncIterator.
To add the resolvers for the subscription, we start by adding a new file called Subscriptions.js
Inside the project directory, add the file as src/resolvers/Subscription.js
Now, in the new file created, add the following code, which will be the subscription resolver for the “newLink” type we created in GraphQL schema.
In the code above, the subscription resolver function, newLinkSubscribe, is added as a field value to the property subscribe just as we described before. The context provides reference to the Pub/Sub object, which lets us use the asyncIterator() with “NEW_LINK” as a parameter. This function resolves subscriptions and publishes events.
Adding Subscriptions to Your Resolvers
The final step for our subscription implementation is to call the function above inside of a resolver. We add the following call to pubsub.publish() inside the post resolver function inside Mutation.js file.
In the code above, we can see that we pass the same string “NEW_LINK” to the publish method as we did in the newLinkSubscribe function in the subscription function before. The “NEW_LINK” is the event name, and it will publish events to the Pusher service, and the same name will be used on the subscription resolver to bind to the particular event name. We also add the newLink as a second argument, which contains the data part for the event that will be published. The context.pubsub.publish function will be triggered before returning the newLink data.
Now, we will update the main resolver object, which is given to the GraphQL server.
First, import the subscription module inside of the index.js file.
Now, with all code in place, we start testing our real time API. We will use multiple instances/tabs of GraphQL playground concurrently.
If your server is already running, then kill it with CTRL+C and restart with this command:
Next, open the browser and navigate to http://localhost:4000 to see the GraphQL playground. We will use one tab of the playground to perform the mutation to trigger the event to Pusher and invoke the subscriber.
We will now start to execute the queries to add some entities in the application.
First, let’s create a user in the application by using the signup mutation. We send the following mutation to the server to create a new User entity.
You will see a response in the playground that contains the authentication token for the user. Copy the token, and open another tab in the playground. Inside that new tab, open the HTTP_HEADERS section in the bottom and add the Authorization header.
Replace the __TOKEN__ placeholder from the below snippet with the copied token from above.
Now, all the queries or mutations executed from that tab will carry the authentication token. With this in place, we sent the following mutation to our GraphQL server.
The mutations above create a Link entity inside the application. Now that we have created an entity, we now move to test the subscription part. In another tab, we will send the subscription query and create a persistent WebSocket connection to the server. Before firing out a subscription query, let us first understand the syntax of it. It starts with the keyword subscription followed by the subscription name. The subscription query is defined in the GraphQL schema and shows the data shape we can resolve to. Here, we want to subscribe to a newLink subscription name, and the data resolved by it consists of that of a Link entity. That means we can resolve any specific part of the Link entity. Here, we are asking for attributes like id, URL, description, and nested attributes of the postedBy field.
The response of this operation is different from that of a mutation or query. You see a loading spinner, which indicates that it is waiting for an event to happen. This means the GraphQL client (playground) has established a connection with the server and is listening for response data.
Before triggering a subscription, we will also keep an eye on the Pusher channel for events triggered to verify that our Pusher service is integrated successfully.
To do this, we go to Pusher dasboard and navigate to the channel app we created and click on the debug console. The debug console will show us the events triggered in real-time.
Now that the Pusher dashboard is visible, we will trigger the subscription event by running the following mutation inside a new Playground tab.
Now, we observe the Playground where subscription was running.
We can see that the newly created Link is visible in the response section, and the subscription continues to listen, and the event has reached the Pusher service.
You will observe an event on the Pusher console that is the same event and data as sent by your post mutation.
We have achieved our first goal, i.e., we have integrated the Pusher channel and implemented a subscription for a Link creation event.
To achieve our second goal, i.e., to listen to Vote events, we repeat the same steps as we did for the Link subscription.
We add a subscription resolver for Vote in the Subscriptions.js file and update the Subscription type in the GraphQL schema. To trigger a different event, we use “NEW_VOTE” as the event name and add the publish function inside the resolver for Vote mutation.
Update the export statement to add the newVote resolver.
Update the Vote mutation to add the publish call before returning the newVote data. Notice that the first parameter, “NEW_VOTE”, is being passed so that the listener can bind to the new event with that name.
Now, restart the server and complete the signup process with setting HTTP_HEADERS as we did before. Add the following subscription to a new Playground tab.
In another Playground tab, send the following Vote mutation to the server to trigger the event, but do not forget to verify the Authorization header. The below mutation will add the Vote of the user to the Link. Replace the “__LINK_ID__” with the linkId generated in the previous post mutation.
Observe the event data on the response tab of the vote subscription. Also, you can check your event triggered on the pusher dashboard.
The final codebase is available on a branch named with-subscription.
By following the steps above, we saw how easy it is to add real-time features to GraphQL apps with subscriptions. Also, establishing a connection with the server is no hassle, and it is much similar to how we implement the queries and mutations. Unlike the mainstream approach, where one has to build and manage the event handlers, the GraphQL subscriptions come with these features built-in for the client and server. Also, we saw how we can use a managed real-time service like Pusher can be for Pub/Sub events. Both GraphQL and Pusher can prove to be a solid combination for a reliable real-time system.