Skip to main content
GraphQL Fundamentals·Lesson 5 of 5

Subscriptions & Real-Time

GraphQL subscriptions push data to clients over a persistent WebSocket connection. When a mutation triggers an event, every subscribed client receives the update instantly.

Server Setup

Install the WebSocket transport:

npm install graphql-ws ws @graphql-tools/schema

Update your server to handle both HTTP and WebSocket connections:

import { createServer }         from 'http';
import { WebSocketServer }      from 'ws';
import { useServer }            from 'graphql-ws/lib/use/ws';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { PubSub }               from 'graphql-subscriptions';

const pubsub = new PubSub();

const typeDefs = `#graphql
  type Message {
    id:        ID!
    text:      String!
    sender:    String!
    createdAt: String!
  }

  type Query {
    messages: [Message!]!
  }

  type Mutation {
    sendMessage(text: String!, sender: String!): Message!
  }

  type Subscription {
    messageSent: Message!
  }
`;

const messages = [];
let nextId = 1;

const resolvers = {
  Query: {
    messages: () => messages,
  },
  Mutation: {
    sendMessage: (_, { text, sender }) => {
      const message = {
        id: String(nextId++),
        text,
        sender,
        createdAt: new Date().toISOString(),
      };
      messages.push(message);
      pubsub.publish('MESSAGE_SENT', { messageSent: message });
      return message;
    },
  },
  Subscription: {
    messageSent: {
      subscribe: () => pubsub.asyncIterator(['MESSAGE_SENT']),
    },
  },
};

const schema = makeExecutableSchema({ typeDefs, resolvers });

const httpServer = createServer(app);
const wsServer   = new WebSocketServer({ server: httpServer, path: '/graphql' });
useServer({ schema }, wsServer);

Client Subscription

import { useSubscription, gql } from '@apollo/client';

const MESSAGE_SENT = gql`
  subscription OnMessageSent {
    messageSent {
      id text sender createdAt
    }
  }
`;

function ChatMessages() {
  const [messages, setMessages] = useState([]);

  useSubscription(MESSAGE_SENT, {
    onData: ({ data }) => {
      setMessages(prev => [...prev, data.data.messageSent]);
    },
  });

  return (
    <ul>
      {messages.map(msg => (
        <li key={msg.id}>
          <strong>{msg.sender}:</strong> {msg.text}
        </li>
      ))}
    </ul>
  );
}

Simulating Real-Time in the Browser

Ctrl+Enter
HTML
CSS
JS
Preview

PubSub in Production

The built-in PubSub from graphql-subscriptions is in-memory — it doesn't work across multiple server instances. For production, replace it with a Redis-backed PubSub:

npm install graphql-redis-subscriptions ioredis
import { RedisPubSub } from 'graphql-redis-subscriptions';
const pubsub = new RedisPubSub({
  connection: { host: 'localhost', port: 6379 }
});

Now publish and subscribe work across any number of server processes.

When to Use Subscriptions

Use caseRecommendation
Chat messagesSubscription ✓
Live notificationsSubscription ✓
Dashboard that refreshes every 30sPolling is simpler
Collaborative editingSubscription ✓
Long-running job statusSubscription or polling

Subscriptions add complexity (WebSocket infrastructure, connection management). Use them when truly real-time behaviour is required.