Subscription
is a GraphQL feature that allows a server to send data to its client when a specific event happens
Prisma comes with out-of-the-box support for subscriptions. For each model in the Prisma datamodel you can subscribe to the following events:
- A new model is created
- An existing model updated
- An existing model is deleted
You can subscribe to these events using the $subscribe
method of the Prisma client.
$subscribe
property proxies the subscriptions from the Prisma API. It is used to resolve subscriptions and push the event data
-
Just like queries and mutations, the first step to implement a subscription is to extend the GraphQL schema definition. Add the following
Subscription
type definition inschema.graphql
file:// ------------------- // src/schema.graphql // ------------------- type Subscription { newLink: Link }
-
The resolvers for subscriptions are slightly different from queries and mutations:
- They return an
AsyncIterator
rather than a data - They are wrapped inside an object and need to be provided as the value for a
subscribe
key in the object. You also need to provide another key calledresolve
that returns the data from the data emitted by theAsyncIterator
Create a new file called
Subcription.js
and add the following implementation in the file:// ------------------- // src/resolvers/Subcription.js // ------------------- function newLinkSubscribe(parent, args, context, info) { return context.prisma.$subscribe.link({ mutation_in: ['CREATED'] }).node(); } const newLink = { subscribe: newLinkSubscribe, resolve: payload => { return payload; } }; module.exports = { newLink };
- They return an
-
Import the module at the top of
index.js
file:// ------------------- // src/index.js // ------------------- const Subscription = require('./resolvers/Subscription');
Still on the same file, update the definition of the
resolvers
object to look as follows:const resolvers = { Query, Mutation, Subscription, User, Link };
Again, restart the GraphQL server...
Open a new Playground and send the following subscription:
subscription {
newLink {
id
url
description
postedBy {
id
name
email
}
}
}
You'll not immediately see the result of the operation, instead you get a loading spinner indicating that it's waiting for an event to happen. Also a text "Listening..." is displayed below the pane.
Now send the following post
mutation in another Playground to trigger a subscription event. Make sure the request is authenticated:
mutation {
post(
url: "www.graphqlweekly.com"
description: "Curated GraphQL content coming to your email inbox every Friday"
) {
id
}
}
You should see a result in the Playground where the subscription is running.
-
Extend the Prisma datamodel to represent votes in the database. To do so, adjust
datamodel.prisma
file to look as follows:// ------------------- // prisma/datamodel.prisma // ------------------- type Link { // ... votes: [Vote!]! } type User { // ... votes: [Vote!]! } type Vote { id: ID! @unique link: Link! user: User! }
Vote
type has one-to-many relationships to theUser
andLink
types. -
To apply the changes and update the Prisma client API to add CRUD operations for
Vote
type, don't forget to deploy the service again with:$ prisma deploy
-
Extend the GraphQL schema definition to expose a
vote
mutation in the GraphQL server:// ------------------- // src/schema.graphql // ------------------- type Mutation { // ... vote(linkId: ID!): Vote }
The referenced
Vote
type also needs to be defined in there:type Vote { id: ID! link: Link! user: User! }
It should also be possible to query all
votes
from aLink
, so adjust theLink
type:type Link { // ... votes: [Vote!]! }
-
Add the following resolver implementation to
Mutation.js
file:// ------------------- // src/resolvers/Mutation.js // ------------------- async function vote(parent, { linkId }, context, info) { // 1 const userId = getUserId(context); // 2 const linkExists = await context.prisma.$exists.vote({ user: { id: userId }, link: { id: linkId } }); if (linkExists) { throw new Error(`Already voted for link: ${linkId}`); } // 3 return context.prisma.createVote({ user: { connect: { id: userId } }, link: { connect: { id: linkId } } }); module.exports = { // ... // 4 vote }; }
1
- similar topost
resolver, it validates the incoming JWT withgetUserId()
. If it's valid the function returns theuserId
of theUser
, otherwise it throws an error2
-$exists
function is generated per model by Prisma client instance. It takes awhere
filter object where you can specify a condition that applies to an element of that type. In this case, is to verify if the requestingUser
(user:{id:userId}
) has not yet voted theLink
(link:{id:args.linkId}
)3
-createVote
is used to create a newVote
that's connected to theUser
and theLink
4
- exports the resolver
-
Account for the new relations in the GraphQL schema:
-
votes
onLink
Add the following function to
Link.js
file:// ------------------- // src/resolvers/Link.js // ------------------- function votes(parent, args, context) { return context.prisma.link({ id: parent.id }).votes(); } module.exports = { // ... votes };
-
user
andlink
onVote
Create a new file called
Vote.js
insideresolvers
folder, and add the following code to it:// ------------------- // src/resolvers/Vote.js // ------------------- function link(parent, args, context) { return context.prisma.vote({ id: parent.id }).link(); } function user(parent, args, context) { return context.prisma.vote({ id: parent.id }).user(); } module.exports = { link, user };
-
-
Finally, include
Vote
resolver in the mainresolvers
object inindex.js
file:// ------------------- // src/index.js // ------------------- const Vote = require('./resolvers/Vote'); const resolvers = { // ... Vote };
The approach is analogous to the newLink
query.
-
Add a new field called
newVote
to theSubscription
type of the GraphQL schema:// ------------------- // src/schema.graphql // ------------------- type Subscription { newLink: Link newVote: Vote }
-
Add its resolver in
Subscription.js
file:// ------------------- // src/resolvers/Subscription.js // ------------------- // ... function newVoteSubscribe(parent, args, context, info) { return context.prisma.$subscribe.vote({ mutation_in: ['CREATED'] }).node(); } const newVote = { subscribe: newVoteSubscribe, resolve: payload => payload }; module.exports = { // ... newVote };
Use the following subscription:
subscription {
newVote {
id
link {
url
description
}
user {
name
email
}
}
}
Test it with the following vote
mutation. Make sure to replace __LINK_ID__
placeholder with the id
of an actual Link
from the database and that the request is authenticated:
mutation {
vote(linkId: "__LINK_ID__") {
link {
url
description
}
user {
name
email
}
}
}