The finished project of this tutorial can be found on GitHub.
One convenient property of GraphQL subscriptions is that they’re using the exact same syntax as queries and mutations. From a client perspective, this means there’s nothing new to learn to benefit from this feature.
The major difference between subscriptions and queries/mutations lies in the execution. While queries and mutations follow typical request-response cycles (just like regular HTTP requests), subscriptions don’t return the requested data right away. Instead, when a GraphQL server receives a subscription request, it creates a long-lived connection to the client which sent the request.
With that request, the client expressed interest in data that’s related to a specific event, for example a specific user liking a picture. The corresponding subscription might look like this:
When the user in question now likes a picture, the server pushes the requested data to the subscribed client via their connection:
Subscriptions are commonly implemented with WebSockets. Apart from the realtime logic (which is typically handled via pub/sub-systems), you need to implement the official communication protocol for GraphQL subscriptions. Only if your server follows the flow defined in the protocol, clients will be able to properly initiate requests and receive event data.
Dealing with realtime logic and pub/sub-systems, properly accessing databases and taking care of implementing the subscription protocol can become fairly complex. Authentication and authorization logic further complicate the implementation of GraphQL subscriptions on the server. In these cases, it’s helpful to use proper abstractions that make your life easier.
One such abstraction is provided by Prisma in combination with Prisma bindings. Think of that combo as a “GraphQL ORM” layer where realtime subscriptions are supported out-of-the-box, making it easy for you to add subscriptions to your API.
The first step in this tutorial is to get access to the starter project. If you don’t want to actually follow the tutorial but are only interested in what the subscription code looks like, feel free to skip ahead.
You can download the starter project from this repository using the following terminal command. Also, directly install the npm dependencies of the project:
The project contains a very simple GraphQL API with the following schema:
The Post type is defined via the Prisma data model and looks as follows:
The goal for this project will be to add two subscriptions to the API:
- A subscription that fires when a new Post is created or the title of an existing Post is updated.
- A subscription that fires when an existing Post is deleted.
Before starting the server, you need to ensure the Prisma database API is available and can be accessed by your GraphQL server (via Prisma bindings).
To deploy the Prisma API, run the yarn prisma deploy command inside the subscriptions-starter directory.
The CLI will then prompt you with a few questions regarding *how *you want to deploy the API. For the purpose of this tutorial, choose any of the Prisma Sandbox options ( sandbox-eu1 or sandbox-us1), then simply hit Enter to select the suggested values for the service name and stage. (Note that if you have Docker installed, you can also deploy locally).
Once the API is deployed, the CLI prints the
HTTP endpoint for the Prisma database API. Copy that endpoint and paste it into
index.js where your
GraphQLServer is instantiated. Note that you need to replace the current placeholder
__PRISMA_ENDPOINT__. After you did this, the code will look similar to this:
You can now start the server and open up a GraphQL Playground by running the
yarn dev command:
Feel free to explore the project and send a few queries and mutations.
Note: The Playground shows you the two GraphQL APIs which are defined in
appproject represents the application layer and is defined by the GraphQL schema in
/src/schema.graphql. The database project represents your database layer and is defined by the auto-generated Prisma GraphQL schema in
**Learn more: ** For an in-depth learning experience, follow the Node tutorial on How to GraphQL
Before starting to implement the subscriptions, let’s take a brief moment to understand the subscription API provided by Prisma since that’s the API you’ll be piggybacking with Prisma bindings.
In general, Prisma lets you subscribe to three different kinds of events (per type in your data model). Taking the
Post type from this tutorial project as an example, these events are:
- a new
- an existing
- an existing
The corresponding definition of the
Subscription type looks as follows (this definition can be found in
If not further constrained through the
where argument, the
post subscription will fire for all of the events mentioned above.
where argument allows clients to specify exactly what events they’re interested in. Maybe a client always only wants to receive updates when a
Post gets deleted or when a
Post where the
title contains a specific keyword is created. These kinds of constraints can be expressed using the where argument. The type of
where is defined as follows:
The two examples mentioned above could be expressed with the following subscriptions in the Prisma API:
You now have a good understanding how you can subscribe to the events that interest you. But how can you now ask for the data related to an event?
PostSubscriptionPayload type defines the fields which you can request in a
post subscription. Here is how that type is defined:
Let’s discuss each of these fields in a bit more detail.
MutationType is an
enum with three values:
mutation field on the
PostSubscriptionPayload type therefore carries the information what kind of mutation happened.
This field represents the
Post element which was created, updated or deleted and allows to retrieve further information about it.
Notice that for
node will always be
null. If you need to know more details about the
Post that was deleted, you can use the
previousValues field instead (more about that soon).
Note: The terminology of a node is sometimes used in GraphQL to refer to single elements. A node essentially corresponds to a record in the database.
One piece of information you might be interested in for UPDATED-mutations is which fields have been updated with a mutation. That’s what the updatedFields field is used for.
Assume a client has subscribed to the Prisma API with the following subscription:
Now, assume the server receives the following mutation to update the
title of a given
The subscribed client will then receive the following payload:
This is because the mutation only updated the Post’s title field - nothing else.
PostPreviousValues type looks very similar to
It basically is a helper type that simply mirrors the fields from
previousValues is only used for
CREATED-mutations, it will always be
null (for the same reason that node is
2.3.5 Putting everything together
Consider again the sample
updatePost-mutation from the section **2.3.3**. But let’s now assume, the subscription query includes *all* the fields we just discussed:
Here’s what the payload will look like that the server pushes to the client after it performed the mutation from before:
Note that this assumes the updated
Post had the following
title before the mutation was performed: “GraphQL servers are best built with conventional ORMs”.
Equipped with the knowledge about the Prisma’s subscription API, you’re now ready to consume precisely that API to implement your own subscriptions on the application layer. Let’s start with the subscription that should fire when a new
Post is created or the
title of an existing
Post is updated.
The first step is to extend the GraphQL schema of your application layer and add the corresponding subscription definition.
schema.graphql and add the following
Subscription type to it:
PostSubscriptionPayload is directly taken from the Prisma GraphQL schema. It thus also needs to be imported at the top of the file:
Note: The comment-based import syntax is used by the
[graphql-import](https://github.com/prismagraphql/graphql-import)package. As of today, GraphQL SDL does not have an official way to import types across files. This might change soon.
Similar to queries and mutations, the next step when adding a new API feature is to implement the corresponding resolver. Resolvers for subscriptions however look a bit different.
Instead of providing only a single resolver function to resolve a subscription operation from your schema definition, you provide an *object *with at least one field called
subscribe field is a function that returns an
AsyncIterator is used to return the values for each individual event. Additionally, you might provide another field called
resolve that we'll discuss in the next section — for now let’s focus on
Update the resolvers object in
index.js to now also include
Prisma bindings are doing the work for you here since
db.subscription.post(...) returns the
AsyncIterator that emits a new value upon every event on the
Note that you’re specifically filtering for
UPDATED-mutations to ensure the publications subscription only fires for those events.
For testing the subscription, you need to start the server and open up a Playground which you can do by running
yarn dev in your terminal.
In the Playground that opened, run the following subscription:
Once the subscription is running, you'll see a loading indicator in the response pane and the Play-button turns into a red Stop-button for you to stop the subscription.
You can now open another tab and send a mutation to trigger the subscription:
Navigating back to the initial tab, you’ll see that the subscription data now appeared in the response pane 🙌
Feel free to play around with the
updateTitle mutation as well.
In this section, you’ll implement a subscription that fires whenever a
Post gets deleted. The process will be largely similar to the publications resolver, except that you’re now going to return just the deleted
Post instead of an object of type
The first step, as usual when adding new features to a GraphQL API, is to express the new operation as a root field in the GraphQL schema.
/src/schema.graphql and adjust the `Subscription type to look as follows:
Instead of returning the
postDeleted, you simply return the
Post object that was deleted.
In section 3.2., we briefly mentioned that the object that you use to implement subscription resolvers can hold a second function called
resolve (next to
subscribe which is required). In this section, you’re going to use it.
Here is what the implementations of both
resolve look like to resolve the
The most important thing to realize about combining the
resolve functions is that the values emitted by the
AsyncIterator (which is returned by
subscribe) correspond to the
payload argument that’s passed into
resolve! This means you can use
resolve to transform and/or filter the event data emitted by the
AsyncIterator according to your needs.
Note that in this scenario, you’re also passing a hardcoded selection set to the
post binding function instead of passing the
info object along as you’re doing most of the time. The invocation of the binding function thus corresponds to the following subscription request against the Prisma API:
The info object carries the AST (and therefore the selection set) of the incoming GraphQL operations (queries, mutations and subscriptions alike). In this case however, the incoming selection set can’t be applied to the
post subscription from the Prisma API. The reasons for that are the following:
The return type of the incoming subscription is simply
Postas you defined in
The return type of the
postsubscription from the Prisma GraphQL API is
This means the incoming
info object does not match the shape that would be required for the
post subscription. Hence, you’re specifying the selection set for the
post subscription manually as a string.
This is a bit tricky to understand at first. If you have trouble following right now, be sure to check out this technical deep-dive about the
infoobject and its role within GraphQL resolvers.
In fact, this situation is not ideal either since for types with many fields, this approach can quickly get out of hand. Also, it might be that the incoming subscription doesn’t request all the fields of a type, so you’re overfetching at this point. The best solution would be to manually retrieve the requested fields from the
info object and pass those along to the
post subscription as described here.
In any case, by hardcoding the selection you’re guaranteed that the payload argument for
resolve has the following structure:
That’s why inside
resolve you can simply return
payload.post.previousValues and what you get is an object that adheres to the structure of the
Post type 💡 (Note that checking for payload with the ternary operator is just a sanity check to ensure it’s not
undefined, since this might break the subscription.)
Before testing the new subscription, you need to restart the server to ensure your changes get applied to the API. You can kill the server by pressing CTRL+C and then restart it using the
yarn dev command.
Once the subscription is running, you can send the following mutation (you need to replace the
__POST_ID__ placeholder with the
id of an actual
Post from your database):
Navigating back to the subscription tab, you’ll see that the
title have been pushed in the response pane, as requested by the active subscription.
Similar to implementing queries and mutations with Prisma, you are piggybacking on Prisma’s GraphQL API, leaving the heavy-lifting of database access and pub/sub logic to the powerful Prisma query engine.
If you want to play around with the project yourself, you can check out the final result of the tutorial on GitHub.
Don’t miss the next post!
Sign up for the Prisma Newsletter