Realtime GraphQL Subscriptions

In this section, you’ll learn how you can bring realtime functionality into your app by implementing GraphQL subscriptions. The goal is to implement two subscriptions to be exposed by your GraphQL server:

  • Send realtime updates to subscribed clients when a new Link element is created
  • Send realtime updates to subscribed clients when an existing Link element is upvoted

What are GraphQL subscriptions?

Subscriptions are a GraphQL feature that allows a server to send data to its clients when a specific event happens. Subscriptions are just part of your GraphQL contract, and they refer to events. To be able to send these events in real-time, you need to choose a transport that has support for that.

In this chapter of the tutorial, you are about to add GraphQL Subscriptions to your server, based on a transport called SSE (Server-Sent Events). This protocol is an extension of simple HTTP, with streaming and real-time capabilities, and doesn’t require any special setup or a new server (as described before, there are many options to implement subscriptions, like WebSockets).

Sever-Sent Events are a way to “upgrade” a basic HTTP request into a long-living request that will emit multiple data items. This is a perfect fit for GraphQL Subscriptions, and implementing and scaling is just simpler than WebSockets.

Implementing GraphQL subscriptions

You’ll be using a simple PubSub implementation from the graphql-subscriptions library to implement subscriptions to the following events:

  • A new model is created
  • An existing model updated
  • An existing model is deleted

Pub/Sub refers to a technique used to create a messaging pattern, where some parts of the code publishes events/messages, and other parts of the code subscribes and being notified about the events/messages. You are going to use that technique in order to create a simple subscription for the GraphQL Subscriptions, based on events published by the GraphQL mutations.

You will do this by first adding an instance of PubSub to the context, just as you did with PrismaClient, and then calling its methods in the resolvers that handle each of the above events.

Setting up PubSub

You’ll use graphql-subscriptions library in order to create an instance of PubSub, and typed-graphql-subscriptions to get better type-safety for the events emitted.

  1. First, you declare a TypeScript type PubSubChannels, you’ll later use that to define your type-safe events.
  2. Then, create an instance of PubSub and combine it with the type-safe events wrapper to form a fully-typed Pub/Sub instance.

Now, you’re adding the global instance of your PubSub and make sure it’s available for your during your GraphQL execution, by injecting it to your context, just as you stored an instance of PrismaClient in the variable prisma.

Great! Now you can access the methods you need to implement our subscriptions from inside our resolvers via context.pubSub!

Subscribing to new Link elements

Alright – let’s go ahead and implement the subscription that allows your clients to subscribe to newly created Link elements.

Just like with queries and mutations, the first step to implement a subscription is to extend your GraphQL schema definition.

Next, go ahead and implement the resolver for the newLink field. Resolvers for subscriptions are slightly different than the ones for queries and mutations:

  1. Rather than returning any data directly, they return an AsyncIterator which subsequently is used by the GraphQL server to push the event data to the client.
  2. Subscription resolvers are wrapped inside an object and need to be provided as the value for a subscribe field. You also need to provide another field called resolve that actually returns the data from the data emitted by the AsyncIterator.

Iterators in JavaScript bring the concept of iteration directly into the core language and provide a mechanism for customizing the behavior of loops. AsyncIterator is a built-in JavaScript types, that allow to to write iterators that might get the values updates in an async way (you can find more information here).

To get started with a new event, first make sure it’s declared correctly by the PubSubChannels. In this case, you are going to declare an event called NEW_LINK, and use the created Link object as payload.

Now that the PubSub knows our events and their payload, you can connect it to your GraphQL subscription resolver.

In the code above, in subscribe function, you are using the context.pubSub to create an instance of AsyncIterable that listens to the newLink event. This will be the trigger for our GraphQL subscriptions. So in case of an active subscription, the AsyncIterable will be created, and a listener for the events will be active.

Then, on every value emitted for that event, you’ll get our resolve function called with the event payload (that matches the structure that you use for our events declaration in PubSubChannels).

Adding subscriptions to your resolvers

The last thing you need to do for our subscription implementation itself is to actually trigger that newLink event from our code!

Now you can see how you pass the same string to the publish method as you added in your subscribe function just above, along with passing in the newLink as a second argument!

Ok, I’m sure you’re dying to test out your brand-spanking new Subscription! All you need to do now is make sure your GraphQL server knows about your changes.

All you need to do in order to test your GraphQL Subscription is to open GraphiQL and try it!

Testing subscriptions

With all the code in place, it’s time to test your realtime API ⚡️ You can do so by using two instances (i.e. browser windows) of the GraphiQL at once.

As you might guess, you’ll use one GraphiQL to send a subscription query and thereby create a permanent websocket connection to the server. The second one will be used to send a post mutation which triggers the subscription.

In contrast to what happens when sending queries and mutations, you’ll not immediately see the result of the operation. Instead, there’s a loading spinner indicating that it’s waiting for an event to happen.

loading spinner

Time to trigger a subscription event.

Now observe the GraphiQL where the subscription was running (left side is the subscription, and right side the mutation)

subscription running

Note: If you see a loading spinner indefinitely, it maybe because of SSE disconnecting when the tab goes idle. Ensure that both the tabs are active.

Adding a voting feature

Implementing a vote mutation

The next feature to be added is a voting feature which lets users upvote certain links. The very first step here is to extend your Prisma data model to represent votes in the database.

As you can see, you added a new Vote type to the data model. It has one-to-many relationships to the User and the Link type.

Now, with the process of schema-driven development in mind, go ahead and extend the schema definition of your application schema so that your GraphQL server also exposes a vote mutation:

hackernews-node-ts/src/schema.graphql
type Mutation {
  post(url: String!, description: String!): Link!
  signup(email: String!, password: String!, name: String!): AuthPayload
  login(email: String!, password: String!): AuthPayload
  vote(linkId: ID!): Vote
}

It should also be possible to query all the votes from a Link, so you need to adjust the Link type in schema.graphql as well.

You know what’s next: Implementing the corresponding resolver functions.

Here is what’s going on:

  1. Make sure that our server don’t allow upvote without being authenticated.
  2. Similar to what you’re doing in the post resolver, the first step is to validate the incoming JWT with the getUserId helper function. If it’s valid, the function will return the userId of the User who is making the request. If the JWT is not valid, the function will throw an exception.
  3. To protect against those pesky “double voters” (or honest folks who accidentally click twice), you need to check if the vote already exists or not. First, you try to fetch a vote with the same linkId and userId. If the vote exists, it will be stored in the vote variable, resulting in the boolean true from your call to Boolean(vote) — throwing an error kindly telling the user that they already voted.
  4. If that Boolean(vote) call returns false, the vote.create method will be used to create a new Vote that’s connected to the User and the Link.
  5. Publish a new event over the Pubsub called newVote.

Now, just like before, we’ll add the event to our typed PubSub.

You also need to account for the new relations in your GraphQL schema:

  • votes on Link
  • user on Vote
  • link on Vote

Similar to before, you need to implement resolvers for these.

Finally you need to resolve the relations from the Vote type.

Your GraphQL API now accepts vote mutations! 👏

Subscribing to new votes

The last task in this chapter is to add a subscription that fires when new Votes are being created. You’ll use an analogous approach as for the newLink query for that.

Next, you need to add the subscription resolver function.

All right, that’s it! You can now test the implementation of your newVote subscription!

You can use the following subscription for that:

subscription {
  newVote {
    id
    link {
      url
      description
    }
    user {
      name
      email
    }
  }
}

If you’re unsure about writing one yourself, here’s a sample vote mutation you can use. You’ll need to replace the __LINK_ID__ placeholder with the id of an actual Link from your database. Also, make sure that you’re authenticated when sending the mutation.

mutation {
  vote(linkId: "__LINK_ID__") {
    link {
      url
      description
    }
    user {
      name
      email
    }
  }
}
Unlock the next chapter
Which of the following statements is true?
Subscriptions follow a request-response-cycle
Subscriptions are best implemented with MailChimp
Subscriptions are a real-time GraphQL contract, and can be implemented with different real-time transports
Subscriptions are defined on the 'Query' type and annotated with the @realtime-directive