Getting Started with GraphQL Subscriptions

Tutorial 1 of 5

Getting Started with GraphQL Subscriptions

1. Introduction

In this tutorial, we'll cover the basics of GraphQL subscriptions. GraphQL subscriptions are a way to push data from the server to the clients whenever data changes. They can be particularly useful in real-time applications, such as live chat, collaboration tools, and monitoring dashboards.

By the end of this tutorial, you'll be able to:

  • Understand the purpose of GraphQL subscriptions
  • Set up and use GraphQL subscriptions in your application
  • Manage data updates in real-time

Prerequisites:

  • Basic knowledge of JavaScript and Node.js
  • Understanding of GraphQL queries and mutations

2. Step-by-Step Guide

Understanding GraphQL Subscriptions

GraphQL subscriptions are a type of operation, like queries and mutations. However, unlike queries and mutations, subscriptions maintain a persistent connection between the client and server. This allows the server to push updates to the client whenever specific events happen.

Setting Up GraphQL Subscriptions

To set up a GraphQL subscription, we need a GraphQL server that supports subscriptions (like Apollo Server), and a client that can maintain a persistent connection to the server.

// server.js
const { ApolloServer, gql, PubSub } = require('apollo-server');
const pubsub = new PubSub();

// define schema
const typeDefs = gql`
  type Subscription {
    messageAdded: String
  }
`;

// define resolvers
const resolvers = {
  Subscription: {
    messageAdded: {
      subscribe: () => pubsub.asyncIterator(['MESSAGE_ADDED']),
    },
  },
};

// create server
const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
});

In this example, we've defined a subscription called messageAdded. The subscribe function returns an async iterator that listens for MESSAGE_ADDED events.

3. Code Examples

Publishing an Event

When something happens on the server (like a new message being added), we can publish an event.

// server.js
// ...
let messageCount = 0;

const resolvers = {
  Mutation: {
    addMessage: (_, { content }) => {
      messageCount++;
      const message = `Message ${messageCount}: ${content}`;
      pubsub.publish('MESSAGE_ADDED', { messageAdded: message });
      return message;
    },
  },
  // ...
};
// ...

In this example, we're publishing a MESSAGE_ADDED event whenever a message is added. The event payload is the new message.

Subscribing to an Event

On the client side, we can subscribe to the messageAdded subscription to receive updates.

// client.js
const { ApolloClient, InMemoryCache, HttpLink, split } = require('@apollo/client');
const { WebSocketLink } = require('@apollo/client/link/ws');
const { getMainDefinition } = require('@apollo/client/utilities');
const gql = require('graphql-tag');

// create Http and WebSocket links
const httpLink = new HttpLink({ uri: 'http://localhost:4000/graphql' });
const wsLink = new WebSocketLink({ uri: 'ws://localhost:4000/graphql', options: { reconnect: true } });

// split links based on operation type
const link = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return definition.kind === 'OperationDefinition' && definition.operation === 'subscription';
  },
  wsLink,
  httpLink,
);

// create client
const client = new ApolloClient({ link, cache: new InMemoryCache() });

// subscribe to messages
const MESSAGE_ADDED_SUBSCRIPTION = gql`
  subscription {
    messageAdded
  }
`;

client.subscribe({ query: MESSAGE_ADDED_SUBSCRIPTION }).subscribe({
  next({ data }) {
    console.log(`Received message: ${data.messageAdded}`);
  },
});

In this example, we're using Apollo Client to subscribe to the messageAdded subscription. Whenever a new message is added, it will be logged to the console.

4. Summary

In this tutorial, we discussed GraphQL subscriptions, which allow us to push updates from the server to the client in real-time. We also walked through a basic example of setting up a GraphQL subscription on the server and subscribing to it from the client.

To further your understanding, you might want to explore how to handle errors and reconnect attempts in GraphQL subscriptions, as well as how to use different transport protocols (like MQTT or SSE) for subscriptions.

5. Practice Exercises

  1. Exercise: Modify the addMessage mutation to take a username argument, and include the username in the published message.

Solution:

const resolvers = {
  Mutation: {
    addMessage: (_, { username, content }) => {
      messageCount++;
      const message = `Message ${messageCount} from ${username}: ${content}`;
      pubsub.publish('MESSAGE_ADDED', { messageAdded: message });
      return message;
    },
  },
  // ...
};

In this modified addMessage mutation, we're taking a username argument and including it in the message.

  1. Exercise: Add a messageCount subscription that publishes the current message count whenever a new message is added.

Solution:

const typeDefs = gql`
  // ...
  type Subscription {
    messageAdded: String
    messageCount: Int
  }
`;

const resolvers = {
  // ...
  Subscription: {
    messageAdded: {
      // ...
    },
    messageCount: {
      subscribe: () => pubsub.asyncIterator(['MESSAGE_COUNT_UPDATED']),
    },
  },
};

// ...
pubsub.publish('MESSAGE_COUNT_UPDATED', { messageCount: messageCount });
// ...

In this messageCount subscription, we're publishing a MESSAGE_COUNT_UPDATED event with the current message count whenever a new message is added.

  1. Exercise: Modify the client to subscribe to the messageCount subscription and log the message count to the console.

Solution:

const MESSAGE_COUNT_SUBSCRIPTION = gql`
  subscription {
    messageCount
  }
`;

client.subscribe({ query: MESSAGE_COUNT_SUBSCRIPTION }).subscribe({
  next({ data }) {
    console.log(`Message count: ${data.messageCount}`);
  },
});

In this modified client, we're subscribing to the messageCount subscription and logging the message count to the console.