Implementing Real-Time Subscriptions with GraphQL and Server-Sent Events in Node.js

User avatar placeholder
Written by Tamzid Ahmed

June 7, 2026

Real-time data streaming has become essential for modern web applications, from live dashboards to collaborative editing tools. In this guide, I’ll walk you through implementing real-time subscriptions in Node.js using two powerful approaches: GraphQL subscriptions and Server-Sent Events (SSE). You’ll understand when to use each method and how to choose the right one for your specific use case.

Understanding Real-Time Communication in Node.js

Real-time communication enables servers to push updates to clients immediately rather than waiting for client requests. This is crucial for applications requiring instant data synchronization across multiple users. Two popular approaches in the Node.js ecosystem are GraphQL subscriptions and Server-Sent Events, each offering distinct advantages depending on your architecture.

Server-Sent Events provide a simple, lightweight mechanism for one-way real-time communication from server to client over a single persistent HTTP connection. GraphQL subscriptions, conversely, leverage the GraphQL type system to deliver typed, structured real-time updates through WebSocket connections. The choice between them significantly impacts your application’s scalability and complexity.

Implementing Server-Sent Events in Node.js

Server-Sent Events offer a straightforward solution for one-way real-time updates. They work natively in browsers without additional libraries and automatically handle connection recovery. Here’s how to implement SSE in Node.js using Express:

First, create an endpoint that sets the proper headers for streaming. The response must remain open, sending data periodically to the client:

const express = require('express');
const cors = require('cors');

const app = express();
app.use(cors());

app.get('/events', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  const clientId = Date.now();
  const clients = new Map();
  clients.set(clientId, res);
  
  req.on('close', () => {
    clients.delete(clientId);
  });
});

To broadcast updates to all connected clients, iterate through your clients collection and write formatted event data:

function broadcastUpdate(data) {
  clients.forEach((client) => {
    client.write(`data: ${JSON.stringify(data)}nn`);
  });
}

Implementing GraphQL Subscriptions in Node.js

GraphQL subscriptions use WebSocket connections to maintain persistent channels between client and server. This approach integrates with your GraphQL schema and type system, enabling type-safe real-time updates. The most common implementation uses the graphql-ws library with Apollo Server or a compatible GraphQL server.

Define your subscription type in your GraphQL schema. This establishes the contract for real-time events:

const typeDefs = gql`
  type Subscription {
    messageReceived(channelId: ID!): Message
  }
  
  type Message {
    id: ID!
    content: String!
    timestamp: String!
  }
`;

Implement the subscription resolver using PubSub from graphql-subscriptions. The resolver function returns an async iterator that emits new values:

const { PubSub } = require('graphql-subscriptions');
const pubsub = new PubSub();

const resolvers = {
  Subscription: {
    messageReceived: {
      subscribe: (_, { channelId }) => pubsub.asyncIterator([`MESSAGE_${channelId}`])
    }
  }
};

// Trigger the subscription when new messages arrive
pubsub.publish(`MESSAGE_${channelId}`, { messageReceived: newMessage });

On the client side, establish the WebSocket connection and subscribe to events using a GraphQL client library.

Comparing Performance and Use Cases

When choosing between GraphQL subscriptions and Server-Sent Events, consider these key factors:

  • Connection overhead: SSE uses standard HTTP with minimal overhead, while GraphQL subscriptions require WebSocket infrastructure
  • Browser support: SSE works natively in all modern browsers; GraphQL subscriptions need client libraries
  • Schema integration: GraphQL subscriptions integrate with your type system for end-to-end type safety
  • Bi-directional communication: GraphQL subscriptions support both sending and receiving; SSE is receive-only
  • Scalability: SSE scales well for simple streaming; GraphQL subscriptions require pub/sub infrastructure for multi-instance deployments

For applications already using GraphQL, subscriptions provide consistent type safety. For simpler real-time dashboards or notification systems, SSE offers lower complexity and better browser compatibility.

Building a Hybrid Implementation

In production environments, you might combine both approaches to leverage their strengths. Use GraphQL subscriptions for complex, type-safe updates within your API, and SSE for simple streaming endpoints or server-sent analytics.

Implement a unified event bus that supports both transport mechanisms. This allows clients to choose the most appropriate method based on their requirements:

class EventBus {
  constructor() {
    this.sseClients = new Map();
    this.graphqlPubsub = new PubSub();
  }
  
  publish(event, payload) {
    // Publish to GraphQL subscriptions
    this.graphqlPubsub.publish(event, payload);
    
    // Broadcast to SSE clients
    this.sseClients.forEach((client) => {
      client.write(`data: ${JSON.stringify({ event, payload })}nn`);
    });
  }
}

Conclusion

Implementing real-time subscriptions in Node.js requires understanding both GraphQL subscriptions and Server-Sent Events. GraphQL subscriptions offer type-safe, schema-integrated real-time updates ideal for complex applications already using GraphQL. Server-Sent Events provide lightweight, browser-native streaming for simpler use cases. Choose based on your architecture requirements, existing tech stack, and scalability needs. For most new projects, starting with SSE for simplicity and migrating to GraphQL subscriptions as requirements grow provides the best balance of initial velocity and long-term maintainability.

Leave a Comment