Mirror: The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.

title: Subscriptions order: 0#

Subscriptions#

One feature of urql that was not mentioned in the "Basics" sections is urql's APIs and ability to handle GraphQL subscriptions.

The Subscription Exchange#

To add support for subscriptions we need to add the subscriptionExchange to our Client.

import { Client, cacheExchange, fetchExchange, subscriptionExchange } from 'urql';

const client = new Client({
  url: 'http://localhost:3000/graphql',
  exchanges: [
    cacheExchange,
    fetchExchange,
    subscriptionExchange({
      forwardSubscription,
    }),
  ],
});

Read more about Exchanges and how they work on the "Authoring Exchanges" page. or what they are on the "Architecture" page.

In the above example, we add the subscriptionExchange to the Client with the default exchanges added before it. The subscriptionExchange is a factory that accepts additional options and returns the actual Exchange function. It does not make any assumption over the transport protocol and scheme that is used. Instead, we need to pass a forwardSubscription function.

The forwardSubscription is called when the subscriptionExchange receives an Operation, so typically, when you’re executing a GraphQL subscription. This will call the forwardSubscription function with a GraphQL request body, in the same shape that a GraphQL HTTP API may receive it as JSON input.

If you’re using TypeScript, you may notice that the input that forwardSubscription receives has an optional query property. This is because of persisted query support. For some transports, the query property may have to be defaulted to an empty string, which matches the GraphQL over HTTP specification more closely.

When we define this function it must return an "Observable-like" object, which needs to follow the Observable spec, which comes down to having an object with a .subscribe() method accepting an observer.

Setting up graphql-ws#

For backends supporting graphql-ws, we recommend using the graphql-ws client.

import { Client, cacheExchange, fetchExchange, subscriptionExchange } from 'urql';
import { createClient as createWSClient } from 'graphql-ws';

const wsClient = createWSClient({
  url: 'ws://localhost/graphql',
});

const client = new Client({
  url: '/graphql',
  exchanges: [
    cacheExchange,
    fetchExchange,
    subscriptionExchange({
      forwardSubscription(request) {
        const input = { ...request, query: request.query || '' };
        return {
          subscribe(sink) {
            const unsubscribe = wsClient.subscribe(input, sink);
            return { unsubscribe };
          },
        };
      },
    }),
  ],
});

In this example, we're creating a SubscriptionClient, are passing in a URL and some parameters, and are using the SubscriptionClient's request method to create a Subscription Observable, which we return to the subscriptionExchange inside forwardSubscription.

Read more on the graphql-ws README.

Setting up subscriptions-transport-ws#

For backends supporting subscriptions-transport-ws, Apollo's subscriptions-transport-ws package can be used.

The subscriptions-transport-ws package isn't actively maintained. If your API supports the new protocol or you can swap the package out, consider using graphql-ws instead.

import { Client, cacheExchange, fetchExchange, subscriptionExchange } from 'urql';
import { SubscriptionClient } from 'subscriptions-transport-ws';

const subscriptionClient = new SubscriptionClient('ws://localhost/graphql', { reconnect: true });

const client = new Client({
  url: '/graphql',
  exchanges: [
    cacheExchange,
    fetchExchange,
    subscriptionExchange({
      forwardSubscription: request => subscriptionClient.request(request),
    }),
  ],
});

In this example, we're creating a SubscriptionClient, are passing in a URL and some parameters, and are using the SubscriptionClient's request method to create a Subscription Observable, which we return to the subscriptionExchange inside forwardSubscription.

Read more about subscription-transport-ws on its README.

Using fetch for subscriptions#

Some GraphQL backends (for example GraphQL Yoga) support built-in transport protocols that can execute subscriptions via a simple HTTP fetch call. In fact, this is how @defer and @stream directives are supported. These transports can also be used for subscriptions.

import { Client, cacheExchange, fetchExchange, subscriptionExchange } from 'urql';

const client = new Client({
  url: '/graphql',
  fetchSubscriptions: true,
  exchanges: [cacheExchange, fetchExchange],
});

In this example, we only need to enable fetchSubscriptions: true on the Client, and the fetchExchange will be used to send subscriptions to the API. If your API supports this transport, it will stream results back to the fetchExchange.

You can find a code example of subscriptions via fetch in an example in the urql repository.

React & Preact#

The useSubscription hooks comes with a similar API to useQuery, which we've learned about in the "Queries" page in the "Basics" section.

Its usage is extremely similar in that it accepts options, which may contain query and variables. However, it also accepts a second argument, which is a reducer function, similar to what you would pass to Array.prototype.reduce.

It receives the previous set of data that this function has returned or undefined. As the second argument, it receives the event that has come in from the subscription. You can use this to accumulate the data over time, which is useful for a list for example.

In the following example, we create a subscription that informs us of new messages. We will concatenate the incoming messages so that we can display all messages that have come in over the subscription across events.

import React from 'react';
import { useSubscription } from 'urql';

const newMessages = `
  subscription MessageSub {
    newMessages {
      id
      from
      text
    }
  }
`;

const handleSubscription = (messages = [], response) => {
  return [response.newMessages, ...messages];
};

const Messages = () => {
  const [res] = useSubscription({ query: newMessages }, handleSubscription);

  if (!res.data) {
    return <p>No new messages</p>;
  }

  return (
    <ul>
      {res.data.map(message => (
        <p key={message.id}>
          {message.from}: "{message.text}"
        </p>
      ))}
    </ul>
  );
};

As we can see, the res.data is being updated and transformed by the handleSubscription function. This works over time, so as new messages come in, we will append them to the list of previous messages.

Read more about the useSubscription API in the API docs for it.

Svelte#

The subscriptionStore function in @urql/svelte comes with a similar API to query, which we've learned about in the "Queries" page in the "Basics" section.

Its usage is extremely similar in that it accepts an operationStore, which will typically contain our GraphQL subscription query.

In the following example, we create a subscription that informs us of new messages.

<script>
  import { gql, getContextClient, subscriptionStore } from '@urql/svelte';

  const messages = subscriptionStore({
    client: getContextClient(),
    query: gql`
      subscription MessageSub {
        newMessages {
          id
          from
          text
        }
      }
    `,
  });
</script>

{#if !$messages.data}
  <p>No new messages</p>
{:else}
  <ul>
    {#each $messages.data.newMessages as message}
      <li>{message.from}: "{message.text}"</li>
    {/each}
  </ul>
{/if}

As we can see, $messages.data is being updated and transformed by the $messages subscriptionStore. This works over time, so as new messages come in, we will append them to the list of previous messages.

subscriptionStore optionally accepts a second argument, a handler function, allowing custom update behavior from the subscription.

Read more about the subscription API in the API docs for it.

Vue#

The useSubscription API is very similar to useQuery, which we've learned about in the "Queries" page in the "Basics" section.

Its usage is extremely similar in that it accepts options, which may contain query and variables. However, it also accepts a second argument, which is a reducer function, similar to what you would pass to Array.prototype.reduce.

It receives the previous set of data that this function has returned or undefined. As the second argument, it receives the event that has come in from the subscription. You can use this to accumulate the data over time, which is useful for a list for example.

In the following example, we create a subscription that informs us of new messages. We will concatenate the incoming messages so that we can display all messages that have come in over the subscription across events.

<template>
  <div v-if="error">
    Oh no... {{error}}
  </div>
  <div v-else>
    <ul v-if="data">
      <li v-for="msg in data">{{ msg.from }}: "{{ msg.text }}"</li>
    </ul>
  </div>
</template>

<script>
import { useSubscription } from '@urql/vue';

export default {
  setup() {
    const handleSubscription = (messages = [], response) => {
      return [response.newMessages, ...messages];
    };

    const result = useSubscription({
      query: `
        subscription MessageSub {
          newMessages {
            id
            from
            text
          }
        }
      `,
    }, handleSubscription)

    return {
      data: result.data,
      error: result.error,
    };
  }
};
</script>

As we can see, the result.data is being updated and transformed by the handleSubscription function. This works over time, so as new messages come in, we will append them to the list of previous messages.

Read more about the useSubscription API in the API docs for it.

One-off Subscriptions#

When you're using subscriptions directly without urql's framework bindings, you can use the Client's subscription method for one-off subscriptions. This method is similar to the ones for mutations and subscriptions that we've seen before on the "Core Package" page.

This method will always returns a Wonka stream and doesn't have a .toPromise() shortcut method, since promises won't return the multiple values that a subscription may deliver. Let's convert the above example to one without framework code, as we may use subscriptions in a Node.js environment.

import { gql } from '@urql/core';

const MessageSub = gql`
  subscription MessageSub {
    newMessages {
      id
      from
      text
    }
  }
`;

const { unsubscribe } = client.subscription(MessageSub).subscribe(result => {
  console.log(result); // { data: ... }
});