GraphQL Subscriptions
01 / 02

Schema, Resolvers & PubSub

Schema, Resolvers & PubSub

Schema & Resolver

type Subscription {
  messageAdded(channelId: ID!): Message
}

type Mutation {
  sendMessage(channelId: ID!, text: String!): Message
}
const { PubSub, withFilter } = require('graphql-subscriptions');
const pubsub = new PubSub();

const resolvers = {
  Mutation: {
    sendMessage: async (_, { channelId, text }) => {
      const message = await db.messages.create({ channelId, text });
      // Publishing is what triggers any active subscription resolvers
      pubsub.publish('MESSAGE_ADDED', { messageAdded: message });
      return message;
    },
  },
  Subscription: {
    messageAdded: {
      // subscribe returns an async iterator yielding a new value per event
      subscribe: withFilter(
        () => pubsub.asyncIterator('MESSAGE_ADDED'),
        // Only deliver to clients subscribed to THIS specific channel —
        // without this, every subscriber gets every channel's messages
        (payload, variables) => payload.messageAdded.channelId === variables.channelId
      ),
    },
  },
};

Client Setup (Apollo Client)

import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
import { split, HttpLink, ApolloClient, InMemoryCache } from '@apollo/client';

const httpLink = new HttpLink({ uri: '/graphql' });
const wsLink = new GraphQLWsLink(createClient({
  url: 'wss://api.example.com/graphql',
  connectionParams: { authToken: getToken() },  // sent once, at connection time
}));

// Route subscription operations to the WebSocket link, everything else to HTTP —
// they need fundamentally different transport mechanics
const splitLink = split(
  ({ query }) => {
    const def = getMainDefinition(query);
    return def.kind === 'OperationDefinition' && def.operation === 'subscription';
  },
  wsLink,
  httpLink,
);

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

Using the Subscription

function ChatWindow({ channelId }) {
  const { data, loading } = useSubscription(MESSAGE_ADDED_SUBSCRIPTION, {
    variables: { channelId },
  });

  useEffect(() => {
    if (data) addMessageToList(data.messageAdded);
  }, [data]);

  return <MessageList />;
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free