While Server Sent Events (SSE) are a good candidate for GraphQL Subscriptions, the is still no full support for it in popular packages. For example the GraphQL Playground only works with Subscriptions over Websockets. Also, in the @nestjs/graphql
package, the recommended package for using GraphQL with NestJS, the implementation of subscription is hard-coded to work with either graphql-ws
or subscription-transport-ws
packages. However, there is a workaround to get things working with @graphql-sse/express
.
Implementing Subscriptions over SSE
With @graphql-sse/express
you have two different endpoints
-
<host>/graphql
: used for GraphQL Queries and Mutations. This is the default behavior added by@nestjs/graphql
-
<host>/graphql-subscription
: used for GraphQL Subscriptions. This endpoint is added by the@graphql-sse/express
package.
Adding Subscription over SSE involves a small update in the main module that imports the GraphQLModule
. When importing the GraphQLModule
we will not use the installSubscriptionHandlers
or subscriptions
option, since they are used for subscription over Websockets. Instead we will use the OnModuleInit
to create a new SubscriptionServer
from the @graphql-sse/express
. The SubscriptionServer
creates a new subscription endpoint with the path /graphql-subscription
. The SubscriptionServer
requires the schema and express App which accessible via Injected GraphQLSchemaHost
and HttpAdapterHost
respectively.
import { Module, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { GraphQLModule, GraphQLSchemaHost } from '@nestjs/graphql';
import { GreetingResolver } from './resolvers/greeting.resolver';
import { SubscriptionServer } from '@graphql-sse/express';
import { HttpAdapterHost } from '@nestjs/core';
@Module({
imports: [
GraphQLModule.forRoot({
autoSchemaFile: true
}),
],
controllers: [],
providers: [
GreetingResolver,
]
})
export class GQLModule implements OnModuleInit , OnModuleDestroy {
private subscriptionServer: SubscriptionServer;
constructor(
private readonly httpAdapterHost: HttpAdapterHost,
private graphQLSchemaHost: GraphQLSchemaHost
) {}
onModuleInit() {
const schema = this.graphQLSchemaHost.schema;
const app = this.httpAdapterHost.httpAdapter.getInstance();
this.subscriptionServer = SubscriptionServer.create({
schema,
app,
});
}
onModuleDestroy() {
this.subscriptionServer.close()
}
}
What is missing?
This solution is still not perfect. It is not as clean as using GraphQLModule.forRoot()
with subscription
option.
Also we cannot use subscriptions in the GraphQL Playground. Hopefully soon, both the @nestjs/graphql
and GraphQL Playground will provide a better abstraction for subscriptions, and allows the usage of any type of transport implementation.
See a full working example here.