GraphQL subscriptions with Nest: how to publish across multiple running servers
Tags: nestjs, graphql, subscriptions, redis
Published: 2019-09-24 23:23:04

Today we'll learn how to setup GraphQL  (GQL) subscriptions using Redis and NestJS.

Prerequisites for this article:

1. An experience in [GraphQL](https://graphql.org/)
1. Some basic knowledge of NestJS (If you don't know what NestJS is, then [give it a try](https://nestjs.com/) and come back after.)
1. [Docker](https://docs.docker.com/) installed on your machine.

You may be asking yourself, "Why we need Redis at all?" A default subscriptions implementation provided by Apollo works out of the box just fine, right?

Well, it depends. When your server has a single instance, you don't need Redis.

But, when you scale the app and spawn extra instances of your server, you need to be sure that events published on one instance will be received by subscribers on the other. This is something default subscription cannot do for you.

So, let's start by building a basic GQL application with the default (*in-memory*) subscriptions.

First, install `@nestjs/cli`:

```bash
npm i -g @nestjs/cli
```

Then create a new NestJS project:

```bash
nest new nestjs-gql-redis-subscriptions
```

![NestJS is generated](https://thepracticaldev.s3.amazonaws.com/i/h21n5sya6vm418x2f8a5.png)

Now, open the `nestjs-gql-redis-subscriptions/src/main.ts` and change

```typescript
await app.listen(3000);
```

to:

```typescript
await app.listen(process.env.PORT || 3000);
```

This allows us to specify a port, when needed, through an env var.

NestJS has a very solid GQL support, but we need to install some extra dependencies to take advantage of it:

```bash
cd nestjs-gql-redis-subscriptions
npm i @nestjs/graphql apollo-server-express graphql-tools graphql graphql-subscriptions
```

We've also installed `graphql-subscriptions`, which brings subscriptions to our app.

To see the subscriptions in action, we're going to build a "ping-pong" app, in which `ping` gets sent via GQL `mutation`, and `pong` gets delivered using GQL `subscription`.

Under the `src` directory, create a `types.graphql` file and put there our schema:

```graphql
type Query {
  noop: Boolean
}

type Mutation {
  ping: Ping
}

type Subscription {
  pong: Pong
}

type Ping {
  id: ID
}

type Pong {
  pingId: ID
}
```

Then go to `app.module.ts`, and import `GraphQLModule` as follows:

```ts
// ... other imports
import { GraphQLModule } from '@nestjs/graphql';
import { PubSub } from 'graphql-subscriptions';

@Module({
  imports: [
    GraphQLModule.forRoot({
      playground: true,
      typePaths: ['./**/*.graphql'],
      installSubscriptionHandlers: true,
    }),
  ],
  providers: [
    {
      provide: 'PUB_SUB',
      useValue: new PubSub(),
    },
  ],
})
export class AppModule {}
```

Let's go through the options we pass to `GraphQLModule.forRoot`:

- `playground` - exposes [GQL Playground](https://github.com/prisma/graphql-playground) on `http:localhost:${PORT}/graphql`. We'll be using this tool for subscribing to "pong" events and sending "ping" mutations. 
- `installSubscriptionHandlers` - enables subscriptions support
- `typePaths` - path to our GQL type definitions.

Another interesting detail is:

```ts
{
  provide: 'PUB_SUB',
  useValue: new PubSub(),
}
```

This is a default (in-memory) implementation of a publish/subscribe engine, which allows us to publish events and create subscriptions.

Now, after we've configured GQL server, it's time to create resolvers. Under the `src` folder, create a file `ping-pong.resolvers.ts`, and enter there following:

```ts
import { Resolver, Mutation, Subscription } from '@nestjs/graphql';
import { Inject } from '@nestjs/common';
import { PubSubEngine } from 'graphql-subscriptions';

const PONG_EVENT_NAME = 'pong';

@Resolver('Ping')
export class PingPongResolvers {
  constructor(@Inject('PUB_SUB') private pubSub: PubSubEngine) {}

  @Mutation('ping')
  async ping() {
    const pingId = Date.now();
    this.pubSub.publish(PONG_EVENT_NAME, { [PONG_EVENT_NAME]: { pingId } });
    return { id: pingId };
  }

  @Subscription(PONG_EVENT_NAME)
  pong() {
    return this.pubSub.asyncIterator(PONG_EVENT_NAME);
  }
}
```

First, we need to decorate `PingPongResolvers` class with `@Resolver('Ping')`. The official NestJS docs does a good job of describing its purpose:

You can consult the Nest.js official documentation on [Working with GraphQL](https://docs.nestjs.com/graphql/quick-start)

> The `@Resolver()` decorator does not affect queries or mutations (neither `@Query()` nor `@Mutation()` decorators). It only informs Nest that each `@ResolveProperty()` inside this particular class has a parent, which is a `Ping` type in this case.

Then, we define our `ping` mutation. Its main responsibility is to publish `pong` event.

Finally, we have our subscription definition, which is responsible for sending the appropriate published events to subscribed clients.

Now we need to add `PingPongResolvers` to our `AppModule`:

```typescript
// ...
@Module({
  // ...
  providers: [
    PingPongResolvers,
    {
      provide: 'PUB_SUB',
      useValue: new PubSub(),
    },
  ],
})
export class AppModule {}
```

At this point, we're ready to start the app, and have a look at our implementation in action.

Actually, to understand the issue with in-memory subscriptions, let's run two instances of our app: one on port *:3000* and another - on *:3001*

In one terminal window, run:

```bash
# port 3000 is the default port for our app
npm start
```

After that, in another one:

```bash
PORT=3001 npm start
```

And here is a demo:

![in-memory subscriptions in action](https://thepracticaldev.s3.amazonaws.com/i/9zghuddpcvortvd2vchk.gif)

As you can see the instance that is running on *:3001* didn't get any events that were published on the *:3000* instance.

Just have a look at the image below to get a view from a different angle:

![in-memory subscriptions under the hood](https://thepracticaldev.s3.amazonaws.com/i/2qj5undhbnay67evk5yv.jpg)

Clearly, there is no way for *:3001* to see events published on *:3000*

Now, let's adjust our app a bit to address this issue. First, we need to install Redis subscriptions dependencies

```bash
npm i graphql-redis-subscriptions ioredis
```

`graphql-redis-subscriptions`  provides a Redis-aware implementation of `PubSubEngine` interface: `RedisPubSub`. You've already used that interface previously, via it's in-memory implementation - `PubSub`.

`ioredis` - is a Redis client, which is used by `graphql-redis-subscriptions`.

To start using our `RedisPubSub`, we just need to tweak `AppModule` a bit.

Change this:

```typescript
// ...
{
  provide: 'PUB_SUB',
  useValue: new PubSub(),
}
// ...
```

To this:

```typescript
// ...
import { RedisPubSub } from 'graphql-redis-subscriptions';
import * as Redis from 'ioredis';
//  ...

// ...
{
  provide: 'PUB_SUB',
  useFactory: () => {
    const options = {
      host: 'localhost',
      port: 6379
    };

    return new RedisPubSub({
      publisher: new Redis(options),
      subscriber: new Redis(options),
    });
  },
},
// ...
```

We're going to start redis in a docker container, and make it available on the `localhost:6379` (which corresponds to options we pass to our `RedisPubSub` instance above):

```bash
docker run -it --rm --name gql-redis -p 6379:6379 redis:5-alpine
```

![Redis is up and running](https://thepracticaldev.s3.amazonaws.com/i/cdi4l7re32u9hcbdre3z.png)

Now we need to stop our apps, and restart them again (in different terminal sessions):

```bash
npm start
```

and

```bash
PORT=3001 npm start
```

At this point, subscriptions work as expected, and events, published on one instance of the app, are received by the client, subscribed to another instance:

![Redis subscriptions in action](https://thepracticaldev.s3.amazonaws.com/i/4wahc456ikbes0cycwoz.gif)

Here is what's happening under the hood:

![Redis subscriptions under the hood](https://thepracticaldev.s3.amazonaws.com/i/4xk2q35iolr78pnbdftd.jpg)

Summary:

In this article, we've learned how to use Redis and GQL subscriptions to publish events across multiple instances of the server app.

We should also better understand GQL subscriptions event pub/sub flow.

Source code:

https://github.com/rychkog/gql-redis-subscriptions-article

*Enjoy this article? Head on over to [This Dot Labs](https://www.thisdot.co/labs) and check us out!  We are a tech consultancy that does all things javascript and front end.  We specialize in open source software like, Angular, React and Vue.*