Subscriptions
GraphQL can be used to perform reads with queries, and to perform writes with mutations. However, oftentimes clients want to get pushed updates from the server when data they care about changes. To support that, GraphQL has a third operation: subscription. TypeGraphQL of course has great support for subscription, using graphql-subscriptions package created by Apollo GraphQL.
Creating subscriptions
Subscription resolvers are basically similar to queries and mutation resolvers but a little bit more complicated.
At first, we create normal class method as always, this time annotated with @Subscription()
decorator.
class SampleResolver {
// ...
@Subscription()
newNotification(): Notification {
// ...
}
}
Then we have to provide to which topics we want to subscribe. This can be a single topic string, an array of topics or a function to dynamically create a topic based on subscription args passed on the query. We can also use TS enums for enhanced type safety.
class SampleResolver {
// ...
@Subscription({
topics: "NOTIFICATIONS", // single topic
topics: ["NOTIFICATIONS", "ERRORS"] // or topics array
topics: ({ args, payload, context }) => args.topic // or dynamic topic function
})
newNotification(): Notification {
// ...
}
}
We can also provide the filter
option to decide which events from topics should trigger our subscription.
This function should return boolean
or Promise<boolean>
.
class SampleResolver {
// ...
@Subscription({
topics: "NOTIFICATIONS",
filter: ({ payload, args }) => args.priorities.includes(payload.priority),
})
newNotification(): Notification {
// ...
}
}
Then we can implement the subscription resolver. It will receive the payload from triggered topic of pubsub system using @Root()
decorator. There we can transform it to the returned shape.
class SampleResolver {
// ...
@Subscription({
topics: "NOTIFICATIONS",
filter: ({ payload, args }) => args.priorities.includes(payload.priority),
})
newNotification(
@Root() notificationPayload: NotificationPayload,
@Args() args: NewNotificationsArgs,
): Notification {
return {
...notificationPayload,
date: new Date(),
};
}
}
Triggering subscriptions topics
Ok, we've created subscriptions, but what is the pubsub
system and how to trigger the topics?
They might be triggered from external sources like a DB. We also can do this in mutations, e.g. when we modify some resource that clients want to receive notifications about the changes.
So, assuming we have this mutation for adding new comment:
class SampleResolver {
// ...
@Mutation(returns => Boolean)
async addNewComment(@Arg("comment") input: CommentInput) {
const comment = this.commentsService.createNew(input);
await this.commentsRepository.save(comment);
return true;
}
}
We use @PubSub()
decorator to inject the pubsub
into our method params.
There we can trigger the topics and send the payload to all topic subscribers.
class SampleResolver {
// ...
@Mutation(returns => Boolean)
async addNewComment(@Arg("comment") input: CommentInput, @PubSub() pubSub: PubSubEngine) {
const comment = this.commentsService.createNew(input);
await this.commentsRepository.save(comment);
// here we can trigger subscriptions topics
const payload: NotificationPayload = { message: input.content };
await pubSub.publish("NOTIFICATIONS", payload);
return true;
}
}
For easier testability (easier mocking/stubbing), we can also inject only the publish
method bound to selected topic.
To do this, we use @PubSub("TOPIC_NAME")
decorator and the Publisher<TPayload>
type:
class SampleResolver {
// ...
@Mutation(returns => Boolean)
async addNewComment(
@Arg("comment") input: CommentInput,
@PubSub("NOTIFICATIONS") publish: Publisher<NotificationPayload>,
) {
const comment = this.commentsService.createNew(input);
await this.commentsRepository.save(comment);
// here we can trigger subscriptions topics
await publish({ message: input.content });
return true;
}
}
And that's it! Now all subscriptions attached to NOTIFICATIONS
topic will be triggered while performing addNewComment
mutation.
Using custom PubSub system
By default, TypeGraphQL use simple PubSub
system from grapqhl-subscriptions
which is based on EventEmitter.
This solution has a big drawback that it will works correctly only when we have single instance (process) of our Node.js app.
For better scalability you'll want to use one of the PubSub implementations backed by an external store.
It might be e.g. Redis with graphql-redis-subscriptions
package.
All you need to do is to create an instance of PubSub according to package instruction and the provide it to TypeGraphQL buildSchema
options:
const myRedisPubSub = getConfiguredRedisPubSub();
const schema = await buildSchema({
resolvers: [__dirname + "/**/*.resolver.ts"],
pubSub: myRedisPubSub,
});
Creating subscription server
The bootstrap guide and all the earlier examples used apollo-server
to create HTTP endpoint for our GraphQL API.
Fortunately, to make subscriptions work, we don't need to manually provide transport layer that doesn't have constraints of HTTP and can do a push-based communication (WebSockets).
The apollo-server
package has built-in subscriptions support using websockets, so it's working out of the box without any changes to our bootstrap config. However, if we want, we can provide the subscriptions
property of config object:
// Create GraphQL server
const server = new ApolloServer({
schema,
subscriptions: {
path: "/subscriptions",
// other options and hooks, like `onConnect`
},
});
And it's done!. We have a working GraphQL subscription server on /subscriptions
, along with a normal HTTP GraphQL server.
Examples
You can see how the subscriptions works in a simple example.
For production usage, it's better to use something more scalable, e.g. a Redis-based pubsub system - working example is also available. However, to launch this example you need to have a running instance of Redis and you might have to modify the example code to provide your connection params.