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:

  1. Subscription: {
  2. commentAdded: {
  3. subscribe: () => pubSub.asyncIterator('commentAdded');
  4. }
  5. }

warning Notice The pubsub is an instance of PubSub class. Read more about it here.

Schema first

To create an equivalent subscription in Nest, we’ll make use of the @Subscription() decorator.

  1. const pubSub = new PubSub();
  2. @Resolver('Author')
  3. export class AuthorResolver {
  4. constructor(
  5. private readonly authorsService: AuthorsService,
  6. private readonly postsService: PostsService,
  7. ) {}
  8. @Query('author')
  9. async getAuthor(@Args('id') id: number) {
  10. return await this.authorsService.findOneById(id);
  11. }
  12. @ResolveProperty('posts')
  13. async getPosts(@Parent() author) {
  14. const { id } = author;
  15. return await this.postsService.findAll({ authorId: id });
  16. }
  17. @Subscription()
  18. commentAdded() {
  19. return pubSub.asyncIterator('commentAdded');
  20. }
  21. }

In order to filter out specific events based on context and arguments, we can set a filter property.

  1. @Subscription('commentAdded', {
  2. filter: (payload, variables) =>
  3. payload.commentAdded.repositoryName === variables.repoFullName,
  4. })
  5. commentAdded() {
  6. return pubSub.asyncIterator('commentAdded');
  7. }

To mutate the published payload, we can use a resolve function.

  1. @Subscription('commentAdded', {
  2. resolve: value => value,
  3. })
  4. commentAdded() {
  5. return pubSub.asyncIterator('commentAdded');
  6. }

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:

  1. @Subscription('commentAdded', {
  2. resolve(this: AuthorResolver, value) {
  3. // "this" refers to an instance of "AuthorResolver"
  4. return value;
  5. }
  6. })
  7. commentAdded() {
  8. return pubSub.asyncIterator('commentAdded');
  9. }

Likewise with filters.

  1. @Subscription('commentAdded', {
  2. filter(this: AuthorResolver, payload, variables) {
  3. // "this" refers to an instance of "AuthorResolver"
  4. return payload.commentAdded.repositoryName === variables.repoFullName;
  5. }
  6. })
  7. commentAdded() {
  8. return pubSub.asyncIterator('commentAdded');
  9. }

Type definitions

The last step is to update type definitions file.

  1. type Author {
  2. id: Int!
  3. firstName: String
  4. lastName: String
  5. posts: [Post]
  6. }
  7. type Post {
  8. id: Int!
  9. title: String
  10. votes: Int
  11. }
  12. type Query {
  13. author(id: Int!): Author
  14. }
  15. type Comment {
  16. id: String
  17. content: String
  18. }
  19. type Subscription {
  20. commentAdded(repoFullName: String!): Comment
  21. }

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.

  1. const pubSub = new PubSub();
  2. @Resolver('Author')
  3. export class AuthorResolver {
  4. constructor(
  5. private readonly authorsService: AuthorsService,
  6. private readonly postsService: PostsService,
  7. ) {}
  8. @Query(returns => Author, { name: 'author' })
  9. async getAuthor(@Args({ name: 'id', type: () => Int }) id: number) {
  10. return await this.authorsService.findOneById(id);
  11. }
  12. @ResolveProperty('posts')
  13. async getPosts(@Parent() author) {
  14. const { id } = author;
  15. return await this.postsService.findAll({ authorId: id });
  16. }
  17. @Subscription(returns => Comment)
  18. commentAdded() {
  19. return pubSub.asyncIterator('commentAdded');
  20. }
  21. }

In order to filter out specific events based on context and arguments, we can set a filter property.

  1. @Subscription(returns => Comment, {
  2. filter: (payload, variables) =>
  3. payload.commentAdded.repositoryName === variables.repoFullName,
  4. })
  5. commentAdded() {
  6. return pubSub.asyncIterator('commentAdded');
  7. }

To mutate the published payload, we can use a resolve function.

  1. @Subscription(returns => Comment, {
  2. resolve: value => value,
  3. })
  4. commentAdded() {
  5. return pubSub.asyncIterator('commentAdded');
  6. }

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.

  1. {
  2. provide: 'PUB_SUB',
  3. useValue: new PubSub(),
  4. }

Module

In order to enable subscriptions, we have to set installSubscriptionHandlers property to true.

  1. GraphQLModule.forRoot({
  2. typePaths: ['./**/*.graphql'],
  3. installSubscriptionHandlers: true,
  4. }),

To customize the subscriptions server (e.g. change port), you can use subscriptions property (read more).