GraphQL Subscriptions with Node.js and Express

Build powerful real-time applications with GraphQL subscriptions. Learn pub/sub patterns, implementation strategies, and production-ready code examples.

What Are GraphQL Subscriptions?

In modern web development, real-time functionality has become essential for delivering dynamic user experiences. From live dashboards and collaborative editing tools to instant messaging and real-time notifications, users expect immediate data updates without manual refreshing. GraphQL subscriptions provide a powerful solution for implementing these real-time features in your Node.js applications.

GraphQL subscriptions represent the third operation type in the GraphQL specification, enabling long-lived connections that push data from the server to clients whenever specific events occur. Unlike queries and mutations that follow a request-response pattern, subscriptions maintain an active connection, allowing for real-time data streaming.

The push-based model fundamentally changes how you build interactive features. Instead of clients repeatedly polling the server for updates, the server proactively sends data when changes occur. This approach reduces unnecessary network traffic, decreases latency, and creates a more responsive user experience. Whether you're building a chat application that needs instant message delivery, a financial dashboard requiring live market data, or a collaborative tool synchronizing changes across multiple users, subscriptions provide the real-time foundation your application needs.

Our web development services regularly incorporate GraphQL subscriptions for clients requiring real-time functionality, demonstrating how this technology integrates with broader application architectures to deliver exceptional user experiences.

How Subscriptions Differ from Queries and Mutations

Understanding the distinction between GraphQL operation types is essential for designing effective APIs. Each type serves a distinct purpose and follows different interaction patterns that together create a complete data management solution.

Queries operate on a request-response model where clients fetch data on-demand. When a client needs information, it sends a query specifying exactly which fields it requires, and the server returns a structured response. This pattern works well for one-time data fetches like loading a user's profile, retrieving a list of products, or fetching configuration settings. Queries are stateless and don't modify any data on the server.

Mutations also follow a request-response pattern but are designed to modify server-side data. When clients need to create, update, or delete information, they send a mutation specifying both the operation and the input data. The server processes the request, performs the requested changes, and returns the result. Like queries, mutations complete their work in a single exchange and close the connection.

Subscriptions fundamentally differ by maintaining persistent connections for push-based updates. Once established, a subscription remains active, with the server initiating data transfer whenever relevant events occur. This persistent connection enables scenarios impossible with queries and mutations alone: live dashboards that update automatically, chat applications that deliver messages instantly, and collaborative tools that synchronize changes across users in real-time.

These three operation types complement each other within a unified API. A typical application might use queries to load initial data, mutations to save user actions, and subscriptions to receive live updates. The same GraphQL schema defines all three, ensuring type consistency across all operations. This unified approach simplifies client development and maintains data integrity throughout the application.

Operation TypeConnection PatternUse CaseExample
QueryRequest-ResponseFetch data on-demandLoading user profile
MutationRequest-ResponseModify server dataPosting a comment
SubscriptionPersistent ConnectionReal-time updatesLive chat messages

When building modern web applications with real-time requirements, understanding these operation types helps you design APIs that leverage GraphQL's full capabilities.

Transport Protocols for Subscriptions

GraphQL subscriptions require a persistent connection protocol to enable real-time data flow. While the GraphQL specification doesn't mandate a specific transport, certain implementations have become standard across the ecosystem. Understanding these transport options helps you choose the right approach for your application's requirements.

WebSockets

WebSockets provide full-duplex communication over a single TCP connection, making them ideal for GraphQL subscriptions. The graphql-ws protocol has become the standard implementation for WebSocket-based subscriptions in the Node.js ecosystem. WebSockets enable bidirectional communication, allowing both the client to send subscription requests and the server to push data updates through the same connection.

WebSockets excel in scenarios requiring high-frequency updates or bidirectional communication. The persistent connection eliminates the overhead of establishing new connections for each message, resulting in lower latency and reduced server load. The graphql-ws library provides a well-tested implementation including connection initialization, subscription management, and proper cleanup on disconnection.

Server-Sent Events (SSE)

SSE offers an alternative for scenarios where WebSockets aren't suitable, providing one-way server-to-client streaming over HTTP. Unlike WebSockets, SSE works over standard HTTP connections and automatically reconnects if the connection drops. However, SSE only supports server-to-client messaging, making it a one-way channel that can't carry bidirectional GraphQL operations.

SSE works well for simple notification systems or dashboards where the client only needs to receive updates. It also handles firewall traversal more easily since it uses standard HTTP ports and doesn't require the upgrade mechanism WebSockets use. However, for complex GraphQL subscriptions with bidirectional communication, WebSockets remain the preferred choice.

The choice between WebSockets and SSE depends on your specific requirements. For full GraphQL subscription functionality including bidirectional communication, WebSockets with graphql-ws provides the most comprehensive solution. For simpler scenarios where you only need server-pushed updates and prefer HTTP-based infrastructure, SSE offers a lightweight alternative. Consider factors like connection limits, firewall restrictions, and the need for bidirectional communication when making your decision.

FeatureWebSocketsServer-Sent Events
CommunicationBidirectionalServer-to-client only
ProtocolTCP (ws://, wss://)HTTP
ReconnectionManualAutomatic
Firewall HandlingMay require configurationStandard HTTP
GraphQL SupportFull (graphql-ws)Limited

When building modern web applications with real-time requirements, our development team evaluates these transport options against your specific needs, choosing the approach that best balances performance, reliability, and infrastructure compatibility.

The Pub/Sub Pattern Explained

GraphQL subscriptions rely on the publish-subscribe pattern to manage event-driven data flow. This architectural pattern provides the foundation for scalable real-time applications by decoupling event producers from event consumers. Understanding pub/sub helps you design systems that can grow from a single server to distributed architectures without fundamental changes to your application logic.

The pattern involves three key participants working together to enable real-time data distribution:

Publishers trigger events when data changes in your application. These can be mutation resolvers that create or update records, background jobs that process data, or external services that notify your system of important events. Publishers don't need to know which clients care about their events--they simply announce that something happened by publishing to a named channel or topic.

The Pub/Sub System acts as an intermediary that receives events from publishers and routes them to all interested subscribers. This layer maintains subscriptions, manages message queuing, and handles the mechanics of delivering events to connected clients. The pub/sub system provides a clean separation between publishers and subscribers, allowing each to operate independently.

Subscribers register interest in specific events by creating subscriptions. When relevant events occur, subscribers receive the data through their persistent GraphQL subscription connections. Subscribers can filter events based on arguments, receiving only the updates that match their criteria.

┌─────────────┐ Publish ┌─────────────┐ Subscribe ┌─────────────┐
│ Publisher │ ───────────────► │ Pub/Sub │ ◄──────────────── │ Subscriber │
│ (Mutation) │ │ System │ │ (Client) │
└─────────────┘ └─────────────┘ └─────────────┘
 │
 │ Broadcast
 ▼
 ┌─────────────┐
 │ Other │
 │ Subscribers │
 └─────────────┘

This decoupling provides significant flexibility and scalability. Publishers and subscribers can scale independently, and new subscribers can be added without modifying the publishers. For production applications running across multiple server instances, the pub/sub system enables event distribution across all instances, ensuring all clients receive updates regardless of which server handles their connection. As noted in advanced GraphQL subscription patterns, this architecture forms the backbone of scalable real-time applications.

Setting Up Your Node.js and Express Environment

Implementing GraphQL subscriptions requires several key dependencies that work together to provide a complete real-time solution. Here's what you'll need for a production-ready setup:

Core Dependencies

@apollo/server serves as the primary GraphQL server implementation, handling queries, mutations, and integrating with Express for HTTP transport. It provides type safety, validation, and a plugin architecture for extending functionality. The server works seamlessly with Express middleware, allowing you to leverage Express routing, middleware, and error handling alongside your GraphQL operations.

@apollo/server/express4 provides the integration layer between Apollo Server and Express, exposing the expressMiddleware function that mounts GraphQL endpoints on your Express application. This integration enables you to combine GraphQL with traditional REST endpoints in the same application.

graphql provides the core GraphQL implementation including type definitions, schema building, and query execution. This peer dependency ensures you have explicit control over the GraphQL version used by your application.

ws is a lightweight WebSocket implementation that graphql-ws uses for handling persistent connections. This library provides the low-level networking needed for real-time communication with subscription clients.

graphql-subscriptions provides the pub/sub abstraction layer that decouples event publishing from subscription handling. The PubSub class offers a simple in-memory implementation for development and serves as an interface for more sophisticated backends like Redis.

graphql-ws implements the graphql-ws protocol for WebSocket-based subscriptions. This library has become the standard for GraphQL subscriptions in the Node.js ecosystem, providing connection lifecycle management, subscription tracking, and proper cleanup.

@graphql-tools/schema provides the makeExecutableSchema function that converts type definitions and resolvers into an executable schema format that Apollo Server and graphql-ws can use.

npm install @apollo/server @apollo/server/express4 express graphql ws graphql-subscriptions graphql-ws @graphql-tools/schema

This combination of packages provides everything needed to run GraphQL operations over both HTTP and WebSocket transports. With these dependencies in place, you're ready to configure your server and define your schema with subscription support. Our team often starts with this foundation when building custom web applications that require real-time capabilities.

For teams exploring modern JavaScript frameworks alongside GraphQL, combining subscriptions with the latest frontend technologies creates powerful, responsive user experiences.

GraphQL Schema with Subscription Type
1type Message {2 id: ID!3 content: String!4 author: String!5 createdAt: String!6}7 8type Query {9 messages: [Message!]!10}11 12type Mutation {13 postMessage(content: String!, author: String!): Message!14}15 16type Subscription {17 messagePosted: Message!18}

Defining Your Subscription Schema

The schema defines the contract between your server and clients. Subscription fields use the subscription keyword and follow similar patterns to queries and mutations, accepting arguments for filtering and specifying the return type for pushed events. A well-designed subscription schema enables clients to express exactly what events they want to receive.

Subscription field names should clearly indicate what event triggers the push. The messagePosted name tells clients they'll receive notifications when new messages are created. Unlike query fields that typically use noun naming (like messages for a list), subscription fields often use past-tense verbs (like messagePosted) to indicate events that have occurred.

Subscription types can accept arguments for filtering events at the server level. For example, a chat application might offer a subscription filtered by room:

type Subscription {
 messagePosted(roomId: ID!): Message!
}

This filtering prevents clients from receiving irrelevant updates and reduces bandwidth usage. The subscription resolver receives these arguments and can apply filters when setting up the pub/sub subscription, ensuring clients only get messages for rooms they care about.

Each subscription should relate to at least one mutation that triggers it. In the example schema, the messagePosted subscription connects to the postMessage mutation. When the mutation calls pubsub.publish(), it triggers all active messagePosted subscriptions. This relationship between mutations and subscriptions creates a clear data flow in your schema that clients can understand.

Consider offering both broad and filtered subscriptions based on your application's needs. A broad messagePosted subscription might suit simple applications, while filtered versions supporting topic, user, or room arguments provide more control for complex scenarios. The key is designing subscriptions that match how your clients actually use real-time updates.

When implementing GraphQL schemas for enterprise web applications, our team designs subscription types that align with business requirements and provide the real-time features users expect.

Complete Apollo Server with WebSocket Setup
1import { ApolloServer } from '@apollo/server';2import { expressMiddleware } from '@apollo/server/express4';3import { ApolloServerPluginDrainHttpServer } from '@apollo/server/plugin/drainHttpServer';4import express from 'express';5import http from 'http';6import { makeExecutableSchema } from '@graphql-tools/schema';7import { WebSocketServer } from 'ws';8import { useServer } from 'graphql-ws/lib/use/ws';9import { PubSub } from 'graphql-subscriptions';10 11const pubsub = new PubSub();12const MESSAGE_POSTED = 'MESSAGE_POSTED';13 14const typeDefs = `#graphql15 type Message {16 id: ID!17 content: String!18 author: String!19 createdAt: String!20 }21 22 type Query {23 messages: [Message!]!24 }25 26 type Mutation {27 postMessage(content: String!, author: String!): Message!28 }29 30 type Subscription {31 messagePosted: Message!32 }33`;34 35const resolvers = {36 Query: {37 messages: () => []38 },39 Mutation: {40 postMessage: (_, { content, author }) => {41 const message = {42 id: Date.now().toString(),43 content,44 author,45 createdAt: new Date().toISOString()46 };47 pubsub.publish(MESSAGE_POSTED, { messagePosted: message });48 return message;49 }50 },51 Subscription: {52 messagePosted: {53 subscribe: () => pubsub.asyncIterator(MESSAGE_POSTED)54 }55 }56};57 58async function startServer() {59 const app = express();60 const httpServer = http.createServer(app);61 const schema = makeExecutableSchema({ typeDefs, resolvers });62 63 const wsServer = new WebSocketServer({64 server: httpServer,65 path: '/graphql'66 });67 68 useServer({ schema }, wsServer);69 70 const server = new ApolloServer({71 schema,72 plugins: [ApolloServerPluginDrainHttpServer({ httpServer })]73 });74 75 await server.start();76 app.use('/graphql', expressMiddleware(server));77 78 await new Promise(resolve => httpServer.listen({ port: 4000 }, resolve));79 console.log('Server ready at http://localhost:4000/graphql');80}81 82startServer();

Server-Side Implementation

The server setup involves coordinating an HTTP server for queries and mutations with a WebSocket server for subscriptions. Understanding how these components work together helps you troubleshoot issues and extend the base configuration for production needs.

The HTTP server handles traditional GraphQL operations using Express middleware. The expressMiddleware function mounts the Apollo Server at the /graphql path, processing queries and mutations through standard HTTP POST requests. This part of your application works identically to REST endpoints, with the GraphQL layer parsing requests, executing operations, and returning JSON responses.

The WebSocket server runs separately but shares the same schema and pub/sub instance. When clients establish WebSocket connections, they connect to the /graphql path where useServer from graphql-ws handles the protocol upgrade and subscription lifecycle. The useServer function takes your schema and integrates it with WebSocket connection handling, automatically managing subscription registration and cleanup.

The ApolloServerPluginDrainHttpServer plugin ensures graceful shutdown by waiting for existing HTTP connections to complete before the server stops. This prevents in-flight requests from being interrupted and is essential for zero-downtime deployments. When combined with proper process signal handling, your server can handle SIGTERM gracefully in containerized environments.

The pub/sub instance sits between mutations and subscriptions, distributing events when data changes. When the postMessage mutation executes, it calls pubsub.publish() with the MESSAGE_POSTED event name. The pub/sub system then delivers this event to all active subscribers listening on that channel. The asyncIterator in the subscription resolver yields new events as they arrive, sending updates to connected clients.

This architecture scales horizontally by sharing the pub/sub backend across multiple server instances. When using Redis or another distributed pub/sub, events published on any server instance reach all subscribers regardless of which server handles their connection. This enables horizontal scaling while maintaining real-time functionality across your server fleet.

When implementing custom solutions for clients, our development team ensures proper error handling, authentication integration, and connection management are built into this foundation from the start.

Pub/Sub Backends and Scaling Strategies

The choice of pub/sub backend significantly impacts your application's scalability, reliability, and operational complexity. Different backends suit different stages of your application lifecycle, from early development through global scale production deployments.

In-Memory Pub/Sub

The built-in PubSub class from graphql-subscriptions works well for development and small-scale applications. It requires no external infrastructure and provides low-latency event delivery within a single process. However, in-memory pub/sub cannot distribute events across multiple server instances and loses events when the process restarts. For development, staging, or applications running as a single instance, it provides a simple starting point without additional infrastructure overhead.

Redis Pub/Sub

For production deployments requiring horizontal scaling, Redis provides a distributed pub/sub solution with minimal additional complexity. Multiple GraphQL server instances connect to a central Redis server, ensuring all clients receive updates regardless of which server handles their connection. Redis pub/sub delivers events with low latency and handles substantial throughput for most applications.

The graphql-redis-subscriptions package provides a RedisPubSub class that implements the same interface as the in-memory version. Switching from in-memory to Redis pub/sub typically involves only changing the import and initialization, without modifying resolver logic that publishes or subscribes to events.

Cloud-Managed Services

Services like AWS SNS/SQS, Google Cloud Pub/Sub, and Azure Service Bus offer managed pub/sub capabilities with strong durability guarantees, automatic scaling, and built-in monitoring. These options suit applications with demanding reliability requirements or teams preferring to avoid infrastructure management.

BackendBest ForScalabilitySetup Complexity
In-MemoryDevelopment, single instanceSingle serverNone
RedisProduction, horizontal scalingMulti-instanceLow
Cloud Pub/SubEnterprise, global scaleAutomaticMedium

Choosing the right backend involves balancing your current needs against future growth. Start with in-memory pub/sub for rapid development, then migrate to Redis when scaling beyond a single server instance. Cloud-managed options become attractive when global distribution or advanced features like message persistence become requirements. This progression allows you to defer infrastructure decisions until you understand your actual scale needs.

Redis PubSub for Distributed Systems
1import { RedisPubSub } from 'graphql-redis-subscriptions';2 3const pubsub = new RedisPubSub({4 publisher: new Redis({5 host: process.env.REDIS_HOST || 'localhost',6 port: 6379,7 retryStrategy: (times) => Math.min(times * 50, 2000)8 }),9 subscriber: new Redis({10 host: process.env.REDIS_HOST || 'localhost',11 port: 6379,12 retryStrategy: (times) => Math.min(times * 50, 2000)13 })14});15 16const MESSAGE_POSTED = 'MESSAGE_POSTED';17 18// Use pubsub exactly like the in-memory version19export { pubsub, MESSAGE_POSTED };

Production Best Practices

Deploying GraphQL subscriptions in production requires attention to several operational concerns that ensure reliability, security, and performance under real-world conditions. These practices help you avoid common pitfalls and build systems that handle production workloads gracefully.

Connection Management

Proper connection management prevents resource leaks and ensures responsive applications under load. Implement proper cleanup of subscriptions on server shutdown using the drain plugin shown in the server setup example. Handle client disconnections gracefully with automatic reconnection logic on the client side, allowing temporary network issues to resolve without losing subscription state.

Set appropriate WebSocket keepalive intervals to detect stale connections. The graphql-ws protocol supports keepalive messages that maintain connections through load balancers and proxies that may close idle connections. Monitor active connection counts and establish limits based on your server capacity to prevent resource exhaustion from connection floods.

Error Handling

Robust error handling prevents subscription failures from crashing your server or leaving clients in undefined states. Catch and log errors in subscription resolvers, distinguishing between transient errors that warrant retry and permanent errors that require client reconnection. Provide meaningful error messages to connected clients while avoiding sensitive implementation details in responses.

Subscription: {
 messagePosted: {
 subscribe: (parent, args, context, info) => {
 try {
 return pubsub.asyncIterator(MESSAGE_POSTED);
 } catch (error) {
 console.error('Subscription error:', error);
 throw new Error('Failed to establish subscription');
 }
 }
 }
}

Implement retry logic for transient failures during connection establishment or event delivery. Validate subscription arguments before establishing connections, rejecting invalid subscriptions early rather than failing later when events arrive.

Security

Authentication for subscriptions requires careful consideration since the connection persists over time. As noted in advanced GraphQL patterns, the subscription connection itself represents an authenticated session that should be verified before allowing subscription registration.

Authorize subscription events to prevent unauthorized data access. Even authenticated users should only receive updates for resources they're permitted to access. Filter events at the resolver level based on the authenticated user's permissions, preventing data leaks through subscription channels.

Use TLS/SSL for WebSocket connections (wss://) to encrypt all data in transit. Implement rate limiting to prevent abuse, both on connection attempts and on the frequency of events published to subscription channels. These protections become increasingly important as your application scales and attracts more attention from potential abusers.

Our web development services incorporate these production considerations from the start, building secure and scalable foundations that grow with your business.

Key Benefits of GraphQL Subscriptions

Why choose subscriptions for your real-time features

Real-Time Updates

Push data to clients instantly when changes occur, eliminating the need for polling and reducing latency dramatically.

Efficient Bandwidth

Send only the data clients need, leveraging GraphQL's field-level selection to minimize network usage.

Unified API

Use the same schema and types for queries, mutations, and subscriptions, simplifying client and server development.

Type Safety

Full type checking across all operation types ensures consistent data contracts between server and client.

Real-World Use Cases for GraphQL Subscriptions

GraphQL subscriptions power real-time functionality across diverse application types, enabling experiences that keep users engaged with fresh, accurate information. Understanding common patterns helps you identify opportunities for subscriptions in your own projects.

Live Dashboards and Analytics

Real-time dashboards display metrics, business intelligence data, and system health indicators that update automatically as new data arrives. Financial platforms show live stock prices, marketing dashboards display campaign metrics as they happen, and DevOps tools show system metrics that help teams respond to issues quickly. The push-based model ensures dashboards always show current information without requiring users to refresh pages.

Chat and Messaging Applications

Chat and messaging platforms rely on instant message delivery for good user experience. Subscriptions handle message delivery, typing indicators, read receipts, and presence updates for collaborative communication platforms. Users see new messages appear immediately, creating the real-time conversation experience they expect from modern messaging apps.

Collaborative Editing Tools

Document collaboration tools synchronize changes across multiple users in real-time, similar to Google Docs functionality. Every keystroke, selection, or edit propagates to all connected users instantly, showing cursor positions and selections from other collaborators. This capability transforms documents from static files into living workspaces where teams can work together effectively.

Notification Systems

Notification systems push alerts, activity notifications, and breaking updates to users instantly without page refreshes. Social networks show activity feeds that update as interactions occur, e-commerce sites alert shoppers to price drops or limited offers, and enterprise systems notify employees of important updates requiring their attention.

Gaming and Interactive Experiences

Multi-player gaming applications broadcast game state changes, player movements, and live scores in real-time. Competitions and live events use real-time updates to keep audiences engaged with scoreboards and activity feeds. These experiences require the low-latency, bidirectional communication that subscriptions provide.

Each use case demonstrates how real-time functionality improves user experience compared to traditional polling approaches. Users receive information immediately, reducing the cognitive overhead of checking for updates and creating more engaging, responsive applications. When planning custom web applications, our team identifies which features benefit from real-time updates and implements subscriptions where they add the most value.

Troubleshooting Common Issues

Even well-implemented subscription systems encounter issues in production. Understanding common problems and their solutions helps you diagnose and resolve issues quickly, minimizing impact on users.

Connection Problems

WebSocket handshake failures typically indicate mismatched configuration between client and server. Verify the WebSocket path matches the server configuration--both should use '/graphql' by convention. Load balancers and proxies may interfere with WebSocket upgrades, requiring configuration to allow the upgrade request through.

Connection drops after initial establishment often relate to network stability or timeout configuration. Check for load balancer idle timeout settings that may close inactive WebSocket connections. Implement reconnection logic on the client with exponential backoff to handle temporary network issues gracefully.

Authentication failures during connection initialization indicate token handling issues. Ensure auth tokens are passed correctly in the connection params and that the server validates tokens before accepting subscriptions. Check token expiration handling, as long-running subscriptions need token refresh strategies.

Memory Leaks

Long-running subscription connections can accumulate memory if not properly managed. Unsubscribe from subscriptions when components unmount in client applications, releasing references that would otherwise persist. Set connection timeouts for inactive clients, closing connections that haven't received events within a defined period.

Monitor memory usage in production environments using profiling tools to identify growing allocations. The graphql-ws library provides connection tracking that helps identify subscriptions that aren't being cleaned up properly. Regular memory profiling catches leaks before they impact production performance.

Scaling Challenges

High-concurrency scenarios require distributed pub/sub backends for horizontal scaling. Use Redis pub/sub for horizontal scaling across multiple server instances, ensuring all clients receive updates regardless of which server handles their connection. Implement connection load balancing across server instances to distribute connection overhead evenly.

Monitor subscription latency and adjust infrastructure as needed. Event delivery time should remain consistent under load; increasing latency indicates capacity issues requiring additional server instances or pub/sub backend optimization. Rate limiting prevents individual clients from overwhelming the system with subscription requests or generating excessive events.

IssueSymptomsSolution
Handshake failureConnection rejected immediatelyVerify path, check proxy config
Dropped connectionsIntermittent disconnectsCheck timeouts, add reconnection
Auth failuresConnection closes after initValidate token handling
Memory growthIncreasing RAM usageAdd cleanup, timeouts
High latencySlow event deliveryScale pub/sub backend

Our development team applies these troubleshooting patterns when supporting enterprise web applications, quickly identifying and resolving issues to maintain reliable real-time functionality.

Frequently Asked Questions

Ready to Add Real-Time Features to Your Application?

Our team specializes in building modern web applications with GraphQL and real-time capabilities. Let's discuss how we can help bring your project to life.

Sources

  1. GraphQL.org: Subscriptions - Official documentation covering subscription fundamentals, pub/sub patterns, and transport protocols
  2. LogRocket: GraphQL subscriptions with Node.js and Express - Practical implementation guide with code examples for Node.js and Express
  3. DEV Community: Mastering Scalable GraphQL Subscriptions - Advanced patterns for scaling and enterprise deployments with distributed systems