Introduction

GraphQL Subscriptions are a powerful feature that allows clients to receive real-time updates from the server. Unlike queries and mutations, which follow a request-response model, subscriptions enable a persistent connection between the client and the server, allowing the server to push updates to the client as they happen.

Key Concepts

  1. What are Subscriptions?

  • Definition: Subscriptions are a way to maintain a real-time connection between the client and the server.
  • Use Cases: Ideal for applications that require real-time updates, such as chat applications, live sports scores, or stock market updates.

  1. How Subscriptions Work

  • Persistent Connection: Subscriptions use WebSockets to maintain a persistent connection.
  • Event-Driven: The server pushes updates to the client when specific events occur.

  1. Setting Up Subscriptions

  • Server-Side: Define subscription types and resolvers.
  • Client-Side: Use a client library to handle the subscription connection.

Practical Example

Step 1: Define Subscription Type in Schema

First, you need to define a subscription type in your GraphQL schema.

type Subscription {
  messageAdded: Message
}

type Message {
  id: ID!
  content: String!
  author: String!
}

Step 2: Implement Subscription Resolver

Next, implement the resolver for the subscription. This example uses Apollo Server.

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

const typeDefs = gql`
  type Subscription {
    messageAdded: Message
  }

  type Message {
    id: ID!
    content: String!
    author: String!
  }

  type Query {
    messages: [Message]
  }

  type Mutation {
    addMessage(content: String!, author: String!): Message
  }
`;

const messages = [];
const MESSAGE_ADDED = 'MESSAGE_ADDED';

const resolvers = {
  Query: {
    messages: () => messages,
  },
  Mutation: {
    addMessage: (parent, { content, author }) => {
      const message = { id: messages.length + 1, content, author };
      messages.push(message);
      pubsub.publish(MESSAGE_ADDED, { messageAdded: message });
      return message;
    },
  },
  Subscription: {
    messageAdded: {
      subscribe: () => pubsub.asyncIterator([MESSAGE_ADDED]),
    },
  },
};

const server = new ApolloServer({
  typeDefs,
  resolvers,
  subscriptions: {
    path: '/subscriptions',
  },
});

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

Step 3: Client-Side Subscription

On the client side, use Apollo Client to subscribe to the messageAdded event.

import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
import { WebSocketLink } from '@apollo/client/link/ws';
import { split } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { HttpLink } from '@apollo/client';

const httpLink = new HttpLink({
  uri: 'http://localhost:4000/',
});

const wsLink = new WebSocketLink({
  uri: `ws://localhost:4000/subscriptions`,
  options: {
    reconnect: true,
  },
});

const splitLink = split(
  ({ query }) => {
    const definition = getMainDefinition(query);
    return (
      definition.kind === 'OperationDefinition' &&
      definition.operation === 'subscription'
    );
  },
  wsLink,
  httpLink
);

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

client
  .subscribe({
    query: gql`
      subscription {
        messageAdded {
          id
          content
          author
        }
      }
    `,
  })
  .subscribe({
    next(data) {
      console.log(data);
    },
  });

Exercises

Exercise 1: Basic Subscription

  1. Task: Implement a basic subscription for a userJoined event.
  2. Steps:
    • Define the userJoined subscription type in the schema.
    • Implement the resolver for the userJoined subscription.
    • Test the subscription using Apollo Client.

Solution

type Subscription {
  userJoined: User
}

type User {
  id: ID!
  name: String!
}
const USER_JOINED = 'USER_JOINED';

const resolvers = {
  Mutation: {
    addUser: (parent, { name }) => {
      const user = { id: users.length + 1, name };
      users.push(user);
      pubsub.publish(USER_JOINED, { userJoined: user });
      return user;
    },
  },
  Subscription: {
    userJoined: {
      subscribe: () => pubsub.asyncIterator([USER_JOINED]),
    },
  },
};
client
  .subscribe({
    query: gql`
      subscription {
        userJoined {
          id
          name
        }
      }
    `,
  })
  .subscribe({
    next(data) {
      console.log(data);
    },
  });

Common Mistakes and Tips

  • WebSocket Connection Issues: Ensure the WebSocket server is correctly configured and running.
  • Subscription Path: Verify the subscription path matches between the server and client.
  • Event Naming: Use consistent and descriptive event names to avoid confusion.

Conclusion

GraphQL Subscriptions provide a robust way to implement real-time features in your applications. By understanding how to define subscription types, implement resolvers, and set up client-side subscriptions, you can create dynamic and responsive applications that keep users updated in real-time.

© Copyright 2024. All rights reserved