How to enable subscription in Apollo Federation with nestJS graphql
NestJS with GraphQL
Setting up a graphQL server in nestJS saves 5x-10x of the effort with vanilla nodejs. NestJS uses the standard apollo library for setting up the graphQL. It supports both normal and federation mode. In federation mode, we can setup graphql servers in every microservices, 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 powerful nodejs framework available as of today. One of the cool features of nestJS is that it allows easy integration with databases, message queues, authentication etc. Further more it allows developers to create application using nodeJS with in a much stricter, robust and flexible framework like Spring Boot / Angular.
If you are new to nestJS head over to https://docs.nestjs.com for getting started. This article expects that you have a prior knowledge of setting up and running nestJS.
Contents
the problem
All this comes with some inherent caveats from the apollo library. While setting up the federation you cannot use graphql subscription.
Apollo had come up with an approach to over come this problem. But it over complicates the situation. Lets see how to tackle this problem using the features in nestJS. In the example i am using the schema first approach. The same approach can be implemented in the code based approach.
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 normal 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 in to different files as per the below conventions.
- *.graphql : for graphql syntax supported in both federation and normal mode
- *.graphql.federation : for syntax supported only in federation mode (eg : extends)
- *.graphql.normal : for syntax supported only in normal mode (eg : subscription)
Save the subscription model in any “graphql.normal” file
You can give any name you would like to. This tutorial follows this naming convention.
step 2 : configure server
Setup 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 the only federation module loads the .graphql.federation
file and only 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 type paths for the two modules are different as per our convention. The normal graphql with subscription is now available at /grapqhl
and the server for federation gateway is available at /graphql-federated
We are not spinning two servers here. Its 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 works in the federated mode and vice versa. You will finally end up in writing the custom version of the grapqhl 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 a 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 federation and normal graphql server.
type Department @key(fields: "id") {
id: ID!
name: String
}
thats all folks
Now you can start the federation gateway which listens to the 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. More efficient than the Apollo method. You can download the entire code for your reference from my github repo (https://github.com/vinodsr/nestjs-graphql-federation-with-subscription).
bonus tip
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 enabled file associations for those extensions.
This is how it looks like in VS Code. Adding the file extensions will give you syntax highlighting and code formatting for the normal and federation graphql.