Subscriptions
Subscription is another GraphQL operation type, like Query and Mutation. It allows creating real-time subscriptions over a bi-directional transport layer, mainly over websockets. Read more about subscriptions here. Below is a commentAdded subscription example, copied directly from the official Apollo documentation.
Subscription: {
commentAdded: {
subscribe: () => pubSub.asyncIterator('commentAdded');
}
}Notice ThepubSubobject is an instance of thePubSubclass. 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');
}
}To filter out specific events based on context and arguments, set the 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 injected providers (e.g., use an external service to validate the data), use the following construction:
@Subscription('commentAdded', {
resolve(this: AuthorResolver, value) {
// "this" refers to an instance of "AuthorResolver"
return value;
}
})
commentAdded() {
return pubSub.asyncIterator('commentAdded');
}The same construction works 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 the 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
}With this, we've created a single commentAdded(repoFullName: String!): Comment subscription. You can find a full sample implementation here.
Code first#
To create a subscription using the code-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');
}
}To filter out specific events based on context and arguments, set the 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 above. The preferred approach is to define PubSub as a provider and inject it through the constructor (using the @Inject() decorator). This allows us to re-use the instance across the whole application.
{
provide: 'PUB_SUB',
useValue: new PubSub(),
}Module#
To enable subscriptions, set the installSubscriptionHandlers property to true.
GraphQLModule.forRoot({
typePaths: ['./**/*.graphql'],
installSubscriptionHandlers: true,
}),To customize the subscriptions server (e.g., change the listener port), use the subscriptions options property (read more).
