Graphql Subscriptions

Author : JaNakh Pon , September 04, 2021

Tags

Summary

Subscriptions allow clients to listen to real-time messages from the server. The client connects to the server with a bi-directional communication channel using the WebSocket protocol and sends a subscription query that specifies which event it is interested in. When an event is triggered, the server executes the stored GraphQL query, and the result is sent back to the client using the same communication channel.

The client can unsubscribe by sending a message to the server. The server can also unsubscribe at any time due to errors or timeouts. A significant difference between queries or mutations and subscriptions is that subscriptions are stateful and require maintaining the GraphQL document, variables, and context over the lifetime of the subscription.


Graphql Subscriptions with Nest.js

We are going to add subscription to repo from the previous article in a few steps:

  1. Installation and setup
  2. Defining methods in resolver/schema for subscriptions

Installation & Schema

Firstly, let's install the required dependency:

  > npm i graphql-subscriptions --save

And add GraphQLModule.forRoot({ installSubscriptionHandlers: true,}) to "app.module.ts"

Now, Let's get started by adding subscriptions to "resolver":

todo.resolver.ts
import { Query, Resolver, Args, ID, Mutation, Subscription } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';
import { RowCount, SearchResponse, SortType, Status, Todo } from '../interfaces/todo.interface'
import { CreateInput, SearchInput, TodoID, TodoObject } from '../types/todo.gql'
import { TodoService } from '../todo.service';
import { UseGuards } from '@nestjs/common';
import { GraphqlJwtAuthGuard } from 'src/user/auth/guard/gql.guard';
import { GqlCurrentUser } from 'src/user/auth/guard/gql.user';
import { User } from 'src/user/interfaces/user.interface';

@Resolver('Todo')
export class TodoResolver {
    private pubSub: PubSub;
    constructor(private todoService: TodoService) {
        this.pubSub = new PubSub();
    }

    @Query()
    @UseGuards(GraphqlJwtAuthGuard)
    async me(@GqlCurrentUser() user: User,): Promise<User> {
        return user
    }

    @Query()
    async list(
        @Args('page') page = 1,
        @Args('take') take = 5,
        @Args('sort') sort = "updated_at",
        @Args('order') order = SortType.D
    ): Promise<Todo[]> {
        return await this.todoService.findAll(page, take, sort, order);
    }

    @Query()
    async searchList(@Args('input') input: SearchInput): Promise<SearchResponse> {
        const { page, take, title, text } = input
        return await this.todoService.search(page, take, title, text)
    }

    @Query()
    async todo(@Args('id', { type: () => ID }) id: number): Promise<Todo> {
        return await this.todoService.findOne(id);
    }

    @Query()
    @UseGuards(GraphqlJwtAuthGuard)
    async count(@GqlCurrentUser() user: any,): Promise<RowCount> {
        console.log("user", user)
        return await this.todoService.getCount()
    }

    @Mutation()
    async create(@Args('input') input: CreateInput): Promise<Todo> {
        const todo = await this.todoService.create(input)
        await this.pubSub.publish('todoCreated', { todoCreated: todo })
        return todo
    }

    @Mutation()
    async update(@Args('id', { type: () => ID }) id: number, @Args('input') input: CreateInput): Promise<Todo> {
        const resp = await this.todoService.update(id, input)
        await this.pubSub.publish('todoUpdated', { todoUpdated: resp })
        return resp
    }

    @Mutation()
    async delete(@Args('id', { type: () => ID }) id: number): Promise<Status> {
        const resp = await this.todoService.delete(id);
        await this.pubSub.publish('todoDeleted', { todoDeleted: id })
        return resp
    }

    @Subscription(() => TodoObject, {
        name: 'todoCreated',
    })
    todoCreated(): AsyncIterator<Todo> {
        return this.pubSub.asyncIterator<Todo>('todoCreated');
    }

    @Subscription(() => TodoObject, {
        name: 'todoUpdated',
    })
    todoUpdated(): AsyncIterator<Todo> {
        return this.pubSub.asyncIterator<Todo>('todoUpdated');
    }

    @Subscription(() => TodoID, {
        name: 'todoDeleted',
    })
    todoDeleted(): AsyncIterator<Todo> {
        return this.pubSub.asyncIterator<Todo>('todoDeleted');
    }
}

And "schema" in "todo.graphql":

todo.graphql
type Todo {
    id: ID!
    title: String
    text: String
    completed: Boolean
}

type SearchResponse {
    data: [Todo]
    count: Int
}

type Status {
    message: String
}

type User {
    userId: Int 
    email: String
}

type RowCount {
    count: Int
}

enum SortType {
    ASC,
    DESC
}

input SearchInput {
    page: Int 
    take: Int 
    title: String
    text: String
}

input CreateInput {
    title: String
    text: String
    completed: Boolean
}

type Query {
    list(page: Int, take: Int, sort: String, order: SortType): [Todo]
    searchList(input: SearchInput): SearchResponse
    todo(id: ID!): Todo!
    me: User
    count: RowCount
}

type Mutation {
    create(input: CreateInput!): Todo
    update(id: ID!, input: CreateInput!):Todo
    delete(id: ID!): Status
}

type Subscription {
    todoCreated: Todo
    todoUpdated: Todo
    todoDeleted: ID
}

Now, we can go to localhost:3001/graphql and play with subscriptions in Graphql Playground! 😉.

Source Code.