Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use GraphQL subscription correctly?

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

like image 298
Hongbo Miao Avatar asked Jul 19 '19 00:07

Hongbo Miao


People also ask

How do I use a GraphQL subscription?

Open your favorite browser and navigate to http://localhost:3000/graphql, you will see the playground UI appear, so run the subscription. The playground will start listening to event updates from the GraphQL server. Create a second tab and load http://localhost:3000/graphql on it.

How do I test my GraphQL subscriptions?

So to test subscriptions we need to change the **describeGQL()** function because it needs some setup: Start the backend subscriptions server (websockets) Create a graphql-subscription client connecting to the server (we will use apollo-client here on the server, just for test) Provide a way for the test to subscribe.

Which pattern is used in GraphQL subscription?

Published by Saurabh Dashora on January 26, 2022. On a high-level, GraphQL follows a typical client-server architectural pattern. The client makes a request and the server provides a response. However, there can be several variations in terms of how to structure the GraphQL server.

What is the difference between a GraphQL query and subscription?

Like queries, subscriptions enable you to fetch data. Unlike queries, subscriptions are long-lasting operations that can change their result over time. They can maintain an active connection to your GraphQL server (most commonly via WebSocket), enabling the server to push updates to the subscription's result.


1 Answers

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.

like image 181
Hongbo Miao Avatar answered Oct 20 '22 10:10

Hongbo Miao