How to Enable Subscription in Apollo Federation With NestJS GraphQL
By default, Apollo does not allow you to enable subscription in a federation server. Here's how to enable subscription in NestJS using Apollo GraphQL.
Join the DZone community and get the full member experience.
Join For FreeSetting up a GraphQL server in NestJS saves 5x-10x of the effort with vanilla NodeJS. NestJS uses the standard Apollo library for setting up GraphQL. It supports both normal and federation mode. In federation mode, we can set up GraphQL servers in every microservice, which can be aggregated using a gateway just like API-Gateway with REST services. It is a very good technique if you are running microservices and want to separate the responsibility for each service.
NestJS is one of the most powerful NodeJS frameworks available as of today. One of the cool features of NestJS is that it allows easy integration with databases, message queues, authentication, etc. Furthermore, it allows developers to create applications using NodeJS within a much stricter, robust, and flexible framework like Spring Boot/Angular.
The Problem
All this comes with some inherent caveats from the Apollo library. While setting up the federation, you cannot use a GraphQL subscription.
The Solution
The solution to this problem is to hide the subscription-based schema for the federation server and host the subscription-based GraphQL as a separate GraphQL server. Seems very simple huh? Not that easy. We will cover the method with some simple steps.
Step 1: Classify Schema
The primary step is to separate your GraphQL schema into different files per the below conventions.
- *.graphql: for GraphQL syntax supported in both federation and normal mode
- *.graphql.federation: for syntax supported only in federation mode (e.g., extends)
- *.graphql.normal: for syntax supported only in normal mode (e.g., subscription)
Save the subscription model in any "graphql.normal" file.
Step 2: Configure the Server
Set up the app.module.ts with two GraphQL modules, one for the normal server and another for the federation server. We need to configure the module in such a way that only the federation module loads the .graphql.federation file and only the normal module loads the .graphql.normal file. The .graphql file has to be loaded by both modules.
imports: [
GraphQLModule.forRoot({
debug: false,
playground: true,
path: '/graphql',
typePaths: ['./**/*.{graphql,graphql.normal}'],
installSubscriptionHandlers: true,
}),
GraphQLFederationModule.forRoot({
debug: false,
playground: false,
path: '/graphql-federated',
typePaths: ['./**/*.{graphql,graphql.federation}'],
}),
],
Notice that the type paths for the two modules are different as per our convention. The normal GraphQL with subscription is now available at /graphql
and the server for the federation gateway is available at /graphql-federated
.
We are not spinning two servers here. It is the same express server with two middleware configured for different paths, so there will not be any performance issues.
Step 3: The Illusion
This is the most important step. There are some directives in GraphQL that only work in the federated mode and vice versa. You will finally end up writing the custom version of the GraphQL model in the federated and the normal files. This will add the headache of duplicate GraphQL models in the application.
This problem can be tackled in an easy way, using dummy directives!
- Define a declarative for “key”
import { SchemaDirectiveVisitor } from 'apollo-server-express';
import { GraphQLField } from 'graphql';
/**
* This is a dummy implementation of the key directive for the normal mode
*/
export class FakeKeyDirective extends SchemaDirectiveVisitor {
/**
* Fake Key definition
* @param _field Field of graphql
*/
visitFieldDefinition(_field: GraphQLField<any, any>) {
_field.args;
}
}
- Include it in the module.
@Module({
imports: [
GraphQLModule.forRoot({
debug: false,
playground: true,
path: '/graphql',
typePaths: ['./**/*.{graphql,graphql.normal}'],
installSubscriptionHandlers: true,
directiveResolvers: {
key: FakeKeyDirective,
},
}),
GraphQLFederationModule.forRoot({
debug: false,
playground: false,
path: '/graphql-federated',
typePaths: ['./**/*.{graphql,graphql.federation}'],
}),
],
controllers: [AppController],
providers: [AppService, UsersResolver, UserService],
})
export class AppModule {}
- Define a fake implementation. This only has to work in the normal mode, so the file name has to end with “
graphql.normal
”
directive @key(fields: String) on OBJECT
- Now you can define the model using the federation-supported @key directive, and the model works both in the federation and normal GraphQL server.
type Department @key(fields: "id") { id: ID! name: String }
Now you can start the federation gateway, which listens to the /graphql-federated
, and the federation works.
For subscription, you can use any Websocket-enabled gateways like Nginx, Istio, etc. and connect directly to the microservices
Conclusion
Yes, it is possible to enable federation and subscription for GraphQL in NestJS using a simple trick that is more efficient than the Apollo method. You can download the entire code for your reference from my Github repo.
Since you are using a separate file extension other than .graphql, your IDE won’t give the native GraphQL formatting for the graphql.normal
and graphql.federation
files. There is a fix for this. Just enable file associations for those extensions.
Published at DZone with permission of Vinod Surendran. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments