Subscriptions
Subscription is just another GraphQL operation type like Query and Mutation. It allows creating real-time subscriptions over a bidirectional transport layer, mainly over websockets. Read more about the subscriptions here. Below is a commentAdded subscription example, copied directly from the official Apollo documentation:
Subscription: {
commentAdded: {
subscribe: () => pubSub.asyncIterator('commentAdded');
}
}Notice Thepubsubis an instance ofPubSubclass. Read more about it here.
Schema first#
To create an equivalent subscription in Nest, we'll make use of the @Subscription() decorator.
const pubSub = new PubSub();
@Resolver('Author')
export class AuthorResolver {
constructor(
private readonly authorsService: AuthorsService,
private readonly postsService: PostsService,
) {}
@Query('author')
async getAuthor(@Args('id') id: number) {
return this.authorsService.findOneById(id);
}
@ResolveProperty('posts')
async getPosts(@Parent() author) {
const { id } = author;
return this.postsService.findAll({ authorId: id });
}
@Subscription()
commentAdded() {
return pubSub.asyncIterator('commentAdded');
}
}In order to filter out specific events based on context and arguments, we can set a filter property.
@Subscription('commentAdded', {
filter: (payload, variables) =>
payload.commentAdded.repositoryName === variables.repoFullName,
})
commentAdded() {
return pubSub.asyncIterator('commentAdded');
}To mutate the published payload, we can use a resolve function.
@Subscription('commentAdded', {
resolve: value => value,
})
commentAdded() {
return pubSub.asyncIterator('commentAdded');
}If you need to access some of the injected providers (e.g. use external service to validate the data), you can use the following construction:
@Subscription('commentAdded', {
resolve(this: AuthorResolver, value) {
// "this" refers to an instance of "AuthorResolver"
return value;
}
})
commentAdded() {
return pubSub.asyncIterator('commentAdded');
}Likewise with filters.
@Subscription('commentAdded', {
filter(this: AuthorResolver, payload, variables) {
// "this" refers to an instance of "AuthorResolver"
return payload.commentAdded.repositoryName === variables.repoFullName;
}
})
commentAdded() {
return pubSub.asyncIterator('commentAdded');
}Type definitions#
The last step is to update type definitions file.
type Author {
id: Int!
firstName: String
lastName: String
posts: [Post]
}
type Post {
id: Int!
title: String
votes: Int
}
type Query {
author(id: Int!): Author
}
type Comment {
id: String
content: String
}
type Subscription {
commentAdded(repoFullName: String!): Comment
}Well done. We created a single commentAdded(repoFullName: String!): Comment subscription. You can find a full sample implementation here.
Code first#
To create a subscription using the class-first approach, we'll make use of the @Subscription() decorator.
const pubSub = new PubSub();
@Resolver('Author')
export class AuthorResolver {
constructor(
private readonly authorsService: AuthorsService,
private readonly postsService: PostsService,
) {}
@Query(returns => Author, { name: 'author' })
async getAuthor(@Args({ name: 'id', type: () => Int }) id: number) {
return this.authorsService.findOneById(id);
}
@ResolveProperty('posts')
async getPosts(@Parent() author) {
const { id } = author;
return this.postsService.findAll({ authorId: id });
}
@Subscription(returns => Comment)
commentAdded() {
return pubSub.asyncIterator('commentAdded');
}
}In order to filter out specific events based on context and arguments, we can set a filter property.
@Subscription(returns => Comment, {
filter: (payload, variables) =>
payload.commentAdded.repositoryName === variables.repoFullName,
})
commentAdded(@Args({ name: 'repoFullName', type: () => String }) repoFullName: string ) {
return pubSub.asyncIterator('commentAdded');
}To mutate the published payload, we can use a resolve function.
@Subscription(returns => Comment, {
resolve: value => value,
})
commentAdded() {
return pubSub.asyncIterator('commentAdded');
}PubSub#
We used a local PubSub instance here. Instead, we should define PubSub as a provider, inject it through the constructor (using @Inject() decorator), and reuse it among the whole application. You can read more about Nest custom providers here.
{
provide: 'PUB_SUB',
useValue: new PubSub(),
}Module#
In order to enable subscriptions, we have to set installSubscriptionHandlers property to true.
GraphQLModule.forRoot({
typePaths: ['./**/*.graphql'],
installSubscriptionHandlers: true,
}),To customize the subscriptions server (e.g. change port), you can use subscriptions property (read more).
