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:
Link
element is createdLink
element is upvotedSubscriptions 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.
You’ll be using a simple PubSub
implementation from the graphql-subscriptions
library to implement subscriptions to the following events:
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.
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.
type PubSubChannels
, you’ll later use that to define your type-safe events.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
!
Link
elementsAlright – 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:
AsyncIterator
which subsequently is used by the GraphQL server to push the event data to the client.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 anasync
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
).
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!
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.
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)
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.
vote
mutationThe 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:
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:
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.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
.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! 👏
The last task in this chapter is to add a subscription that fires when new Vote
s 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
}
}
}