Skip to content
All essays
CraftMarch 28, 202412 min

GraphQL Subscriptions and Real-time Data

Master GraphQL subscriptions for real-time applications

Ü
Ümit Uz
Mobile & Full Stack Developer

GraphQL subscriptions enable real-time data streaming from server to client. They're essential for building collaborative applications, live dashboards, and any feature requiring instant updates.

Understanding Subscriptions

What Are Subscriptions?

Subscriptions are long-lived operations that push data to clients when specific events occur. Unlike queries and mutations, subscriptions maintain an open connection for continuous updates.

Subscription vs REST

REST - Polling:

javascript
// Poll every 5 seconds
setInterval(async () => {
  const data = await fetch('/api/messages');
  updateMessages(data);
}, 5000);

GraphQL - Real-time:

graphql
subscription {
  newMessages {
    id
    content
    author
    timestamp
  }
}

WebSocket Setup

Server Setup with Apollo Server

javascript
import { ApolloServer } from '@apollo/server';
import { WebSocketServer } from 'ws';
import { useServer } from '@graphql-ws/lib/use/ws';
import { createServer } from 'http';

const typeDefs = `#graphql
  type Message {
    id: ID!
    content: String!
    author: String!
    timestamp: DateTime!
  }

  type Query {
    messages: [Message!]
  }

  type Mutation {
    postMessage(content: String!): Message!
  }

  type Subscription {
    newMessage: Message!
  }
`;

const resolvers = {
  Query: {
    messages: () => messages
  },
  Mutation: {
    postMessage: (_, { content }, { pubsub }) => {
      const message = {
        id: Date.now().toString(),
        content,
        author: currentUser,
        timestamp: new Date().toISOString()
      };
      messages.push(message);
      pubsub.publish('NEW_MESSAGE', { newMessage: message });
      return message;
    }
  },
  Subscription: {
    newMessage: {
      subscribe: (_, __, { pubsub }) => {
        return pubsub.asyncIterator(['NEW_MESSAGE']);
      }
    }
  }
};

Express Server with WebSocket

javascript
import express from 'express';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';
import { makeExecutableSchema } from '@graphql-tools/schema';
import { WebSocketServer } from 'ws';
import { useServer } from 'graphql-ws/lib/use/ws';
import http from 'http';

const app = express();
const httpServer = http.createServer(app);

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

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

const serverCleanup = useServer({ schema }, wsServer);

const server = new ApolloServer({
  schema,
  plugins: [
    ApolloServerPluginDrainHttpServer({ httpServer }),
    {
      async serverWillStart() {
        return {
          async drainServer() {
            await serverCleanup.dispose();
          }
        };
      }
    }
  ]
});

await server.start();
app.use('/graphql', expressMiddleware(server));

const PORT = 4000;
httpServer.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}/graphql`);
});

Client-Side Subscriptions

Apollo Client Setup

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

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

const wsLink = new GraphQLWsLink(createClient({
  url: 'ws://localhost:4000/graphql',
  connectionParams: {
    authToken: userToken
  }
}));

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()
});

Using Subscriptions in React

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

const NEW_MESSAGE_SUBSCRIPTION = gql`
  subscription OnNewMessage {
    newMessage {
      id
      content
      author
      timestamp
    }
  }
`;

function MessageList() {
  const { data, loading, error } = useSubscription(
    NEW_MESSAGE_SUBSCRIPTION
  );

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <Message message={data.newMessage} />
    </div>
  );
}

Subscribing to Multiple Events

javascript
const MESSAGES_SUBSCRIPTION = gql`
  subscription OnMessagesUpdates($channel: String!) {
    messageUpdates(channel: $channel) {
      id
      content
      type
      timestamp
    }
  }
`;

function Channel({ channelId }) {
  const { data, error } = useSubscription(
    MESSAGES_SUBSCRIPTION,
    { variables: { channel: channelId } }
  );

  useEffect(() => {
    if (data) {
      if (data.messageUpdates.type === 'NEW') {
        addMessage(data.messageUpdates);
      } else if (data.messageUpdates.type === 'EDIT') {
        updateMessage(data.messageUpdates);
      }
    }
  }, [data]);
}

Advanced Subscription Patterns

Filtering Subscriptions

graphql
type Subscription {
  messageUpdates(roomId: ID!): Message!
}

const resolvers = {
  Subscription: {
    messageUpdates: {
      subscribe: withFilter(
        (_, { roomId }, { pubsub }) => {
          return pubsub.asyncIterator([`MESSAGE_${roomId}`]);
        },
        (payload, variables) => {
          return payload.messageUpdates.roomId === variables.roomId;
        }
      )
    }
  }
};

Authenticated Subscriptions

javascript
const onConnect = async (ctx) => {
  const token = ctx.connectionParams.authToken;

  try {
    const user = await verifyToken(token);
    return { user };
  } catch (err) {
    throw new Error('Authentication failed');
  }
};

const wsServer = new WebSocketServer({
  server: httpServer,
  path: '/graphql',
  onConnect
});

Pagination with Subscriptions

javascript
const usePaginatedSubscription = (query) => {
  const [items, setItems] = useState([]);
  const [cursor, setCursor] = useState(null);

  const { data } = useSubscription(query, {
    variables: { cursor }
  });

  useEffect(() => {
    if (data?.items) {
      setItems(prev => [...prev, ...data.items]);
      setCursor(data.cursor);
    }
  }, [data]);

  return { items, loadMore: () => setCursor(cursor) };
};

Subscription Best Practices

Connection Management

javascript
function SubscriptionComponent() {
  const client = useApolloClient();

  useEffect(() => {
    const subscription = client.subscribe({
      query: NEW_MESSAGE_SUBSCRIPTION
    }).subscribe({
      next: (data) => console.log('New message', data),
      error: (err) => console.error('Subscription error', err)
    });

    return () => {
      subscription.unsubscribe();
    };
  }, []);
}

Error Handling

javascript
const { data, error } = useSubscription(
  NEW_MESSAGE_SUBSCRIPTION,
  {
    onError: (error) => {
      console.error('Subscription error:', error);
      // Implement reconnection logic
      reconnect();
    }
  }
);

Performance Optimization

javascript
// Debounce rapid updates
import { debounce } from 'lodash';

const debouncedUpdate = debounce((data) => {
  updateUI(data);
}, 300);

useSubscription(UPDATE_SUBSCRIPTION, {
  onData: ({ data }) => {
    debouncedUpdate(data);
  }
});

Common Use Cases

Real-Time Chat

javascript
const CHAT_SUBSCRIPTION = gql`
  subscription OnMessage($chatId: ID!) {
    messageAdded(chatId: $chatId) {
      id
      content
      sender {
        id
        name
        avatar
      }
      createdAt
    }
  }
`;

Live Dashboard

javascript
const METRICS_SUBSCRIPTION = gql`
  subscription OnMetricsUpdate {
    metricsUpdated {
      cpuUsage
      memoryUsage
      activeUsers
      requestsPerSecond
    }
  }
`;

Collaborative Editing

javascript
const DOCUMENT_SUBSCRIPTION = gql`
  subscription OnDocumentChange($documentId: ID!) {
    documentChanged(documentId: $documentId) {
      id
      content
      version
      editedBy {
        id
        name
      }
      timestamp
    }
  }
`;

Conclusion

GraphQL subscriptions enable powerful real-time features. Master them, and you'll build engaging, collaborative applications that keep users connected and informed.

Enjoyed this essay? Share it.

Related essays

Next essay
Mastering Web Vitals: Core Performance Metrics Guide