How to use GraphQL subscription correctly?
Asked Answered
T

1

7

I have a GraphQL powered app. The query and mutation parts work well. I try to add GraphQL subscription.

The server GraphQL subscription part code is inspired by the demo in the readme of apollographql/subscriptions-transport-ws.

Please also check the comments in the code for more details.

import Koa from 'koa';
import Router from 'koa-router';
import graphqlHTTP from 'koa-graphql';
import asyncify from 'callback-to-async-iterator';
import { SubscriptionServer } from 'subscriptions-transport-ws';
import firebase from 'firebase-admin';
import { execute, subscribe } from 'graphql';
import { GraphQLObjectType, GraphQLString } from 'graphql';

const MeType = new GraphQLObjectType({
  name: 'Me',
  fields: () => ({
    name: { type: GraphQLString },
    // ...
  }),
});

const listenMe = async (callback) => {
  // Below the firebase API returns real-time data
  return firebase
    .database()
    .ref('/users/123')
    .on('value', (snapshot) => {
      // snapshot.val() returns an Object including name field.
      // Here I tested is correct, it always returns { name: 'Rose', ... }
      // when some other fields inside got updated in database.
      return callback(snapshot.val());
    });
};

const Subscription = new GraphQLObjectType({
  name: 'Subscription',
  fields: () => ({
    meChanged: {
      type: MeType,
      subscribe: () => asyncify(listenMe),
    },
  }),
});

const schema = new GraphQLSchema({
  query: Query,
  mutation: Mutation,
  subscription: Subscription,
});

const app = new Koa();
app
  .use(new Router()
    .post('/graphql', async (ctx) => {
      // ...

      await graphqlHTTP({
        schema,
        graphiql: true,
      })(ctx);
    })
    .routes());

const server = app.listen(3009);

SubscriptionServer.create(
  {
    schema,
    execute,
    subscribe,
  },
  {
    server,
    path: '/subscriptions',
  },
);

I am using Altair GraphQL Client to test since it supports GraphQL subscription.

enter image description here

As the screenshot shows, it does get new data every time when the data changes in database.

However, meChanged is null and it does not throw any error. Any idea? Thanks

Transliterate answered 19/7, 2019 at 0:48 Comment(2)
I don't know what alway means and you don't include the error message from the Network panel in Chrome Dev Tools so diagnosing your problem is difficult. However, have you looked at this: #56319637Sheepfold
@Sheepfold Thanks! Just updated the title. I hope I could post Chrome console error message, but I haven’t started to build subscription part for client yet, since it is lack of document of using GraphQL subscription without any framework like Apollo. That is why I use Altair GraphQL Client as a start point to help me understand how GraphQL subscription works.Transliterate
T
5

Finally have a new library can do the work without full Apollo framework.

https://github.com/enisdenjo/graphql-ws

Here are the codes that I have succeed:

Server (GraphQL Schema Definition Language)

import { useServer } from 'graphql-ws/lib/use/ws';
import WebSocket from 'ws';
import { buildSchema } from 'graphql';

const schema = buildSchema(`
  type Subscription {
    greeting: String
  }
`);

const roots = {
  subscription: {
    greeting: async function* sayHiIn5Languages() {
      for (const hi of ['Hi', 'Bonjour', 'Hola', 'Ciao', 'Zdravo']) {
        yield { greeting: hi };
      }
    },
  },
};

const wsServer = new ws.Server({
  server, // Your HTTP server
  path: '/graphql',
});
useServer(
  {
    schema,
    execute,
    subscribe,
    roots,
  },
  wsServer
);

Server (GraphQL.js GraphQLSchema object way)

import { execute, subscribe, GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
import { useServer } from 'graphql-ws/lib/use/ws';
import WebSocket from 'ws';
import { PubSub } from 'graphql-subscriptions';

const pubsub = new PubSub();

const subscription = new GraphQLObjectType({
  name: 'Subscription',
  fields: {
    greeting: {
      type: GraphQLString,
      resolve: (source) => {
        if (source instanceof Error) {
          throw source;
        }
        return source.greeting;
      },
      subscribe: () => {
        return pubsub.asyncIterator('greeting');
      },
    },
  },
});

const schema = new GraphQLSchema({
  query,
  mutation,
  subscription,
});

setInterval(() => {
  pubsub.publish('greeting', {
    greeting: 'Bonjour',
  });
}, 1000);

const wsServer = new ws.Server({
  server, // Your HTTP server
  path: '/graphql',
});
useServer(
  {
    schema,
    execute,
    subscribe,
    roots,
  },
  wsServer
);

Client

import { createClient } from 'graphql-ws';

const client = createClient({
  url: 'wss://localhost:5000/graphql',
});

client.subscribe(
  {
    query: 'subscription { greeting }',
  },
  {
    next: (data) => {
      console.log('data', data);
    },
    error: (error) => {
      console.error('error', error);
    },
    complete: () => {
      console.log('no more greetings');
    },
  }
);

DISCLOSE: I am not associated with the library.

Transliterate answered 17/9, 2020 at 14:10 Comment(8)
I tried the first server ie,(Server (GraphQL Schema Definition Language)) and client code as it is to do one POC but i got following error :- "Subscription field must return Async Iterable. Received: {}.". Can you please let me know what i might be doing wrong as i'm a noob in graphql subscriptionsFad
@Fad check github.com/Hongbo-Miao/hongbomiao.com/blob/main/api/src/graphQL/… which might help you. It is a full working demo.Transliterate
thanks for this. I want to clarify one thing in the above link you have used graphql-subscriptions module. Is it necessary to do subscrption using this? Can't we just use graphql-ws, ws and graphql to do graphql susbcriptions? As i was following code given on github page of graphql-ws. That was not working. If you want i can share my github repo also if you can go through that it having just two files with logicFad
@Fad the GraphQL Schema Definition Language way in the answer is also using graphql-subscriptions with graphql-ws, which is same.Transliterate
but when i searched on google to do subscriptions in graphql i also this graphql-subscriptions is separate thing than graphql-ws. Might i'be wrong. I thought both these are different approaches to do susbcription in graphqlFad
@Fad graphql-ws is same level with subscriptions-transport-ws. And graphql-subscriptions is different level thing.Transliterate
how to handle variables in graphql subscription query passed by client at server here, can you give any example? tried seeing the documentation but not able to get this throughFad
Your first example has import WebSocket from 'ws'; but then you're doing ws.Server, which is correct?Caudex

© 2022 - 2024 — McMap. All rights reserved.