How GraphQL Subscriptions Work - Dgraph Blog

In an application, a frontend client sends requests to a backend server and gets responses. Sometimes, the backend needs to notify the client whenever that response changes: for example, any app that involves notifications. Applications that use real-time data, like live scores, also need a mechanism where the server can push an updated response to a query depending on events.

In this blog, we see how to achieve this using GraphQL subscriptions.

What is GraphQL Subscription?

A GraphQL server usually supports a set of queries and mutations (operations) which forms the data interaction between the client and the server. The client sends a query or mutation and gets a response back from the server.

Subscriptions are a way to push data from the server to the clients that choose to listen to real-time messages from the server. Subscriptions are similar to queries in that they specify a set of fields to be delivered to the client, but instead of immediately returning a single answer, a result is sent every time a particular event happens on the server without the client needing to resend that request.

This can be achieved when the client opens a bi-directional communication channel with the server and sends a subscription query that specifies which event it is interested in. When an event is triggered, the server executes the stored GraphQL query, and the result is sent through the same communication channel opened by the client. One way to achieve this is by using Websockets, which establish a two-way interactive communication session between the user's browser and a server. This is different from the HTTP calls used for regular GraphQL queries, where a request is sent, a response received, and the connection closed.

The client can unsubscribe by sending a message to the server. The server can also unsubscribe at any time due to errors or timeout. Another significant difference between queries/mutations and a subscription is that subscriptions are stateful, and thus require maintaining the GraphQL document, variables and context over the lifetime of the subscription. For example, in the following diagram, the server must remember the subscription, requested fields etc. from the client and use those to return a result in response to an event.

Example with Dgraph GraphQL and NextJS

Now that we have a basic understanding of GraphQL subscription, let's quickly create a small example with Dgraph GraphQL, NextJS and Apollo Client.

In Dgraph GraphQL it's very easy to enable subscription on any type. We just mention the directive @withSubscription in the schema along with the type definition.

Create a basic schema and store it in a file schema.graphql.

type Todo @withSubscription {
  id: ID!
  title: String!
  description: String!
  completed: Boolean!
}

Running

There two ways of doing this - by running Dgraph ourselves or by using Slash GraphQL, a fully managed service.

Get your invite for Slash GraphQL here.

This blog follows the approach of running Dgraph locally, though you can easily use Slash GraphQL as well and the steps remain the same with few minor tweaks. Check out the Slash quickstart for more info.

Let's start Dgraph locally.

docker pull dgraph/standalone:master
docker run -it -p 8080:8080 dgraph/standalone:master

That started the Dgraph server. Now let's submit the GraphQL schema.

curl -X POST localhost:8080/admin/schema --data-binary '@schema.graphql'

Create a NextJS app for the UI

This will run an interactive CLI tool which will ask you to enter the name of the project (“todo-nextjs”) and then select a template, let's choose “Default starter app” for this example. Since we're using npx (npm package runner) we don't need to install the package, we can directly run it.

Install Apollo client and it's dependencies that we will need.

cd todo-nextjs 
npm install @apollo/client graphql @apollo/link-ws subscriptions-transport-ws

Setup Apollo Client in our pages/index.js file.

We want to use the WebSocket link only when it's a subscription query and for all the other queries/mutations we wish to use the HTTP link. Let's do that by reading the definition of the query being sent using the getMainDefinition function.

Add the condition depending on the query definition.

import {
  ApolloClient,
  HttpLink,
  InMemoryCache,
  ApolloProvider,
  split,
} from "@apollo/client";
import { getMainDefinition } from "@apollo/client/utilities";
import { WebSocketLink } from "@apollo/link-ws";
const httpLink = new HttpLink({
  uri: "http://localhost:8080/graphql",
});
const wsLink = process.browser
  ? new WebSocketLink({
      uri: `ws://localhost:8080/graphql`,
      options: {
        reconnect: true,
      },
    })
  : null;
const splitLink = process.browser
  ? split(
      ({ query }) => {
        const definition = getMainDefinition(query);
        return (
          definition.kind === "OperationDefinition" &&
          definition.operation === "subscription"
        );
      },
      wsLink,
      httpLink
    )
  : httpLink;
const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: splitLink,
});

Now quickly set up a UI to see GraphQL subscription in action.

Install semantic UI for that.

npm install semantic-ui-react semantic-ui-css

Create a file components/TodoList.js and add the subscription query there. We just wrap our query with the subscription keyword.

subscription {
    queryTodo {
      title
      description
      completed
    }
  }

Then we pass it to the useSubscription hook, using which we subscribe to the above query, so now whenever a todo is added/removed/updated, the server pushes the changed data to the client which triggers a re-render of the react component, without needing to refresh the page.

import { useSubscription, gql } from "@apollo/client";
import { Card, Image } from "semantic-ui-react";
const GET_TODOS = gql`
  subscription {
    queryTodo {
      title
      description
      completed
    }
  }
`;
const TodoList = () => {
  const { loading, error, data } = useSubscription(GET_TODOS);
  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error :(</p>;
  return (
    <Card.Group>
      {data.queryTodo.map(({ id, title, description, completed }) => (
        <Card>
          <Image
            src="/diggy.png"
            wrapped
            ui={false}
          />
          <Card.Content>
            <Card.Header>{title}</Card.Header>
            <Card.Meta>{completed ? "Done" : "Pending"}</Card.Meta>
            <Card.Description>{description}</Card.Description>
          </Card.Content>
        </Card>
      ))}
    </Card.Group>
  );  
};
export default TodoList;

Now start the app and visit http://localhost:3000/.

Let's test this out by adding a todo through Insomnia or any other GraphQL client of your choice.

Fire the following mutation from your GraphQL client which points to your local Dgraph (http://localhost:8080/graphql) or Slash GraphQL instance (if you're using Slash GraphQL) and see your UI update in real-time!

mutation {
  addTodo(input: [{title: "Todo2", description: "subscription", completed: true}]) {
    numUids
  }
}

Refer to the GitHub repo here, for the complete setup of the NextJS app.

Wrapping up

In this blog, we learnt the workings of GraphQL subscription. Then we created an example app with Dgraph GraphQL, which gives a quick and easy way to add subscription to your app.

Check out our docs to find more cool features that we are building! ReferencesTop image: This image from NASA Spitzer Space Telescope shows the Orion nebula, our closest massive star-making factory, 1,450 light-years from Earth. The nebula is close enough to appear to the naked eye as a fuzzy star in the sword of the constellation.


This is a companion discussion topic for the original entry at https://dgraph.io/blog/post/how-does-graphql-subscription/
2 Likes