Skip to main content
Version: Next ๐Ÿšง

GraphQL Subscriptions

GraphQL Subscriptions (GQLS) are a mechanism which allow clients to subscribe to changes in a piece of data from the server, and get notified whenever that data changes.

A GraphQL Subscription looks very similar to a query, with the exception that it uses the subscription keyword:

subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) {  feedback_like_subscribe(data: $input) {    feedback {      id      like_count    }  }}
  • Subscribing to the above subscription will notify the client whenever the specified Feedback object has been "liked" or "unliked". The feedback_like_subscription field is the subscription field itself, which takes specific input and will set up the subscription in the backend.
  • feedback_like_subscription returns a specific GraphQL type which exposes the data we can query in the subscription payload; that is, whenever the client is notified, it will receive the subscription payload in the notification. In this case, we're querying for the Feedback object with its updated like_count, which will allows us to show the like count in real time.

An example of a subscription payload received by the client could look like this:

{  "feedback_like_subscribe": {    "feedback": {      "id": "feedback-id",      "like_count": 321,    }  }}

In Relay, we can declare GraphQL subscriptions using the graphql tag too:

const {graphql} = require('react-relay');
const feedbackLikeSubscription = graphql`  subscription FeedbackLikeSubscription($input: FeedbackLikeSubscribeData!) {    feedback_like_subscribe(data: $input) {      feedback {        id        like_count      }    }  }`;
  • Note that subscriptions can also reference GraphQL variables in the same way queries or fragments do.

There are two ways of executing a subscription against the server. The requestSubscription API and using hooks.

Request subscription API#

In order to execute a subscription against the server in Relay, we can use the requestSubscription API:

import type {Environment} from 'react-relay';import type {FeedbackLikeSubscribeData} from 'FeedbackLikeSubscription.graphql';
const {graphql, requestSubscription} = require('react-relay');
function feedbackLikeSubscribe(  environment: Environment,  feedbackID: string,  input: FeedbackLikeSubscribeData,) {  return requestSubscription(environment, {    subscription: graphql`      subscription FeedbackLikeSubscription(        $input: FeedbackLikeSubscribeData!      ) {        feedback_like_subscribe(data: $input) {          feedback {            id            like_count          }        }      }    `,    variables: {input},    onCompleted: () => {} /* Subscription established */,    onError: error => {} /* Subscription errored */,    onNext: response => {} /* Subscription payload received */  });}
module.exports = {subscribe: feedbackLikeSubscribe};

Let's distill what's happening here:

  • requestSubscription takes an environment, the graphql tagged subscription, and the variables to use.
  • Note that the input for the subscription can be Flow typed with the autogenerated type available from the FeedbackLikeSubscription.graphql module. In general, the Relay will generate Flow types for subscriptions at build time, with the following naming format: *<subscription_name>*.graphql.js.
  • requestSubscription also takes an onCompleted and onError callbacks, which will respectively be called when the subscription is successfully established, or when an error occurs.
  • requestSubscription also takes an onNext callback, which will be called whenever a subscription payload is received.
  • When the subscription payload is received, if the objects in the subscription payload have IDs, the records in the local store will automatically be updated with the new field values from the payload. In this case, it would automatically find the existing Feedback object matching the given ID in the store, and update the values for the like_count field.
  • Note that any local data updates caused by the subscription will automatically cause components subscribed to the data to be notified of the change and re-render.

However, if the updates you wish to perform on the local data in response to the subscription are more complex than just updating the values of fields, like deleting or creating new records, or adding and removing items from a connection, you can provide an updater function to requestSubscription for full control over how to update the store:

import type {Environment} from 'react-relay';import type {CommentCreateSubscribeData} from 'CommentCreateSubscription.graphql';
const {graphql, requestSubscription} = require('react-relay');
function commentCreateSubscribe(  environment: Environment,  feedbackID: string,  input: CommentCreateSubscribeData,) {  return requestSubscription(environment, {    subscription: graphql`      subscription CommentCreateSubscription(        $input: CommentCreateSubscribeData!      ) {        comment_create_subscribe(data: $input) {          feedback_comment_edge {            cursor            node {              body {                text              }            }          }        }      }    `,    variables: {input},    updater: store => {      const feedbackRecord = store.get(feedbackID);
      // Get connection record      const connectionRecord = ConnectionHandler.getConnection(        feedbackRecord,        'CommentsComponent_comments_connection',      );
      // Get the payload returned from the server      const payload = store.getRootField('comment_create_subscribe');
      // Get the edge inside the payload      const serverEdge = payload.getLinkedRecord('feedback_comment_edge');
      // Build edge for adding to the connection      const newEdge = ConnectionHandler.buildConnectionEdge(        store,        connectionRecord,        serverEdge,      );
      // Add edge to the end of the connection      ConnectionHandler.insertEdgeAfter(connectionRecord, newEdge);    },    onCompleted: () => {} /* Subscription established */,    onError: error => {} /* Subscription errored */,    onNext: response => {} /* Subscription payload received */,  });}
module.exports = {subscribe: commentCreateSubscribe};

Let's distill this example:

  • updater takes a store argument, which is an instance of a RecordSourceSelectorProxy; this interface allows you to imperatively write and read data directly to and from the Relay store. This means that you have full control over how to update the store in response to the subscription payload: you can create entirely new records, or update or delete existing ones. The full API for reading and writing to the Relay store is available here: https://facebook.github.io/relay/docs/en/relay-store.html
  • In our specific example, we're adding a new comment to our local store when we receive a subscription payload notifying us that a new comment has been created. Specifically, we're adding a new item to a connection; for more details on the specifics of how that works, check out our Updating Connections section.
  • Note that the subscription payload is a root field record that can be read from the store, specifically using the store.getRootField API. In our case, we're reading the comment_create_subcribe root field, which is a root field in the subscription response.
  • Note that any local data updates caused by the mutation updater will automatically cause components subscribed to the data to be notified of the change and re-render.

Requesting a subscription with Hooks#

You can also use hooks to subscribe to a subscription query.

import {graphql, useSubscription} from 'react-relay';import {useMemo} from 'react';
const subscription = graphql`subscription ...`;function MyFunctionalComponent({ id }) {  // IMPORTANT: your config should be memoized, or at least not re-computed  // every render. Otherwise, useSubscription will re-render too frequently.  const config = useMemo(() => { variables: { id }, subscription }, [id]);  useSubscription(config);  return <div>Move Fast</div>}

This is only a thin wrapper around the requestSubscription API. It's behavior:

  • Subscribe when the component is mounted with the given config
  • Unsubscribe when the component is unmounted

If you have the need to do something more complicated, such as imperatively requesting a subscription, please use the requestSubscription API directly.

Configuring the Network Layer#

You will need to Configure your Network Layer to handle subscriptions. The below example uses subscriptions-transport-ws:

import {    ...    Network,    Observable} from 'relay-runtime';import { SubscriptionClient } from 'subscriptions-transport-ws';
...
const subscriptionClient = new SubscriptionClient('ws://localhost:3000', {    reconnect: true,});
const subscribe = (request, variables) => {    const subscribeObservable = subscriptionClient.request({        query: request.text,        operationName: request.name,        variables,    });    // Important: Convert subscriptions-transport-ws observable type to Relay's    return Observable.from(subscribeObservable);};
const network = Network.create(fetchQuery, subscribe);
...

Is this page useful?