0

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.

Rasyue
  • 60
  • 1
  • 9

1 Answers1

0

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!

Rasyue
  • 60
  • 1
  • 9