Search code examples
graphqlloopback4graphql-subscriptions

How to enable graphql subscription in loopback 4 with openapi-to-graphql


as per the title, I am having problem trying to enable graphql subscription in my loopback 4 application.

Here is my code that I've done so far.

index.ts


export async function main(options: ApplicationConfig = {}) {

 const app = new BackendLb4Application(options)
 await app.boot()
 await app.start()
    
 const url = app.restServer.url;

 const oas: Oas3 = <Oas3><unknown>await app.restServer.getApiSpec()
    
   
 const {schema} = await createGraphQLSchema(oas, {
  operationIdFieldNames: true,
  baseUrl: url,
  createSubscriptionsFromCallbacks: true,
    
 })

 const handler = graphqlHTTP( (request:any, response:any, graphQLParams: any) => ({
    schema,
    pretty: true,        
    graphiql: true    
 }))

 app.mountExpressRouter(graphqlPath, handler);
 
 const pubsub = new PubSub()
 const ws = createServer(app);

 ws.listen(PORT, () => {
   new SubscriptionServer(
   {
     execute,
     subscribe,
     schema,
    onConnect: (params: any, socket: any, ctx: any) => {
                console.log(params, 'here on onconnect')
              // Add pubsub to context to be used by GraphQL subscribe field
              return { pubsub }
            }
          },
          {
            server: ws,
            path: '/subscriptions'
          }
        )
      })

 return app

}


Here is my schema

type Subscription {
  """
  
  
  Equivalent to PATCH onNotificationUpdate
  """
  postRequestQueryCallbackUrlApiNotification(secondInputInput: SecondInputInput): String

  """
  
  
  Equivalent to PATCH onNotificationUpdate
  """
  postRequestQueryCallbackUrlOnNotificationUpdate(firstInputInput: FirstInputInput): String
}

Here is an example of my controller

@patch('/notification-update', {
    operationId: 'notificationUpdate',
    description: '**GraphQL notificationUpdate**',
    callbacks:[ {
      
        onNotificationUpdate: {
          //'{$request.query.callbackUrl}/onNotificationUpdate': {
            post: {
              requestBody: {
                operationId: 'notificationUpdateCallback',
                description: 'rasjad',
                content: {
                  'application/json': {
                    schema: {
                      title: "firstInput",
                      type: 'object',
                      properties: {
                        userData: {
                          type: "string"
                        }
                      }
                    }
                  }
                }
              },
              responses: {
                '200': {
                  description: 'response to subscription',
                }
              }
            }
          },
       // }
    }],
   
    responses: {
      '200': {
        description: 'Notification PATCH success count',
        content: {'application/json': {schema: CountSchema}},
      },
    },
  })

  async updateAll(
    @requestBody({
      content: {
        'application/json': {
          schema: getModelSchemaRef(Notification, {partial: true}),
        },
      },
    })
    notification: Notification,
    @param.where(Notification) where?: Where<Notification>,
  ): Promise<Count> {
    return this.notificationRepository.update(notification, where);
  }

Ive defined the callbacks object in my controller which will then create a subscription in my schema. Tested it out on graphiql but did not work.

I am not sure where to go from here. Do I need a custom resolver or something? Not sure. Appreciate it if anyone could help on this.


Solution

  • Just in case someone else is looking to do the same thing.

    I switched out graphqlHTTP with Apollo Server to create my graphql server.

    So my final index.ts looks like this.

    
    export async function main(options: ApplicationConfig = {}) {
     
    
        const lb4Application = new BackendLb4Application(options)
    
        await lb4Application.boot()
        await lb4Application.migrateSchema()
    
        await lb4Application.start()
        
        const url = lb4Application.restServer.url;
        
    
        const graphqlPath = '/graphql'
    
        // Get the OpenApiSpec
        const oas: Oas3 = <Oas3><unknown>await lb4Application.restServer.getApiSpec()
        // Create GraphQl Schema from OpenApiSpec
        
        const {schema} = await createGraphQLSchema(oas, {
            strict: false,
            viewer: true,
            baseUrl: url,
            headers: {
                'X-Origin': 'GraphQL'
            },
            createSubscriptionsFromCallbacks: true,
    
            customResolvers: {
                "lb4-title": {
                    "your-path":{
                        patch: (obj, args, context, info) => {
                            const num = Math.floor(Math.random() * 10);
                            pubsub.publish("something", { yourMethodName: {count: num} }).catch((err: any) => {
                                console.log(err)
                            })
                            return {count: 1}
                        }
                    }
                }
            },
            customSubscriptionResolvers: {
                "lb4-title" : {
                    "yourMethodName": {
                        post: {
                            subscribe: () => pubsub.asyncIterator("something"),
                            resolve: (obj: any, args: any, context, info) => {
                                console.log(obj, 'obj')
                                
                            }
                           
                        }
                    }
                }
            }
        
        })
        
    
       
        const app = express();
       
        const server = new ApolloServer({
            schema,
            plugins: [{
                async serverWillStart() {
                  return {
                    async drainServer() {
                        subscriptionServers.close();
                    }
                  };
                }
            }],
           
        })
       
    
        
        const subscriptionServers = SubscriptionServer.create(
            {
                // This is the `schema` we just created.
                schema,
                // These are imported from `graphql`.
                execute,
                subscribe,
            }, 
            {
                
                server: lb4Application.restServer.httpServer?.server,
                path: server.graphqlPath,
                //path: server.graphqlPath,
            }
        );
    
       
        
        await server.start();
        server.applyMiddleware({ app, path: "/" });
    
    
        lb4Application.mountExpressRouter('/graphql', app);
    
        
        return lb4Application
    }
    
    

    Also you will need to define the callbacks object in your controller like so.

    @patch('/something-update', {
        operationId: 'somethingUpdate',
        description: '**GraphQL somethingUpdate**',
       
        callbacks:[ 
          {
            yourMethodName: {
              post: {
                responses: {
                  '200': {
                    description: 'response to subscription',
                    content: {'application/json': {schema: CountSchema}},
                    
                  }
                }
              }
            },
          }
        ],
    
        responses: {
          '200': {
            description: 'Something PATCH success count',
            content: {'application/json': {schema: CountSchema}},
          },
        },
      })
      async updateAll(
        @requestBody({
          content: {
            'application/json': {
              schema: getModelSchemaRef(Something, {partial: true}),
            },
          },
        })
        something: Something,
        @param.where(Something) where?: Where<Something>,
      ): Promise<Count> {
        return this.somethingRepository.updateAll(something, where);
      }
    
    

    And that is it. You can test it out from the GraphQL Playground and play around with the subscriptions.

    For the time being, I am fine with defining customResolvers and customSubscriptionResolvers but I'm pretty sure I can automate this two objects from the controllers.

    Cheers!