GraphQL und Server-Sent Events als Alternative zu Subscriptions
AI generated
{ }
type
GraphQL · Server-Sent Events · Realtime · WebSocket-Alternative
GraphQL und Server-Sent Events
als Alternative zu Subscriptions

WebSocket-basierte GraphQL Subscriptions bringen bidirektionale Kommunikation mit, die die meisten Echtzeit-Anwendungsfälle gar nicht brauchen. Server-Sent Events bieten als GraphQL Subscriptions Alternative einen einfacheren, HTTP-nativen Weg für unidirektionale Updates, ohne eigenes Protokoll, eigenen Reconnect-Mechanismus und zusätzliche Infrastruktur.

17 Min. Lesezeit graphql-sse · EventSource · HTTP/2 Realtime-Updates · Node.js · React

1. Warum WebSocket-Subscriptions oft zu viel des Guten sind

GraphQL Subscriptions über WebSocket lösen ein Problem, das die meisten Anwendungen gar nicht haben: bidirektionale Kommunikation in Echtzeit. Ein Bestellstatus-Update, ein neuer Chat-Kommentar oder ein Preis-Ticker fließen fast immer nur in eine Richtung, vom Server zum Client. Trotzdem greifen viele Teams reflexartig zu WebSocket-Subscriptions, weil sie der Standardweg für Echtzeit in GraphQL sind, und ziehen sich damit ein eigenes Protokoll, einen persistenten Verbindungszustand und zusätzliche Infrastrukturkomplexität ins Haus.

Server-Sent Events als GraphQL Subscriptions Alternative passen genau auf diesen unidirektionalen Fall. SSE ist Teil des HTML-Standards, läuft über gewöhnliches HTTP und braucht keinen Protokoll-Umschalt-Handshake wie WebSocket. Für Anwendungsfälle wie Live-Benachrichtigungen, Fortschrittsanzeigen bei lang laufenden Jobs oder Dashboards mit periodischen Updates ist das oft die schlankere und robustere Wahl.

Der Wechsel zu Server-Sent Events bedeutet keinen Verzicht auf Echtzeit-Funktionalität, sondern eine bewusste Reduktion auf das, was tatsächlich gebraucht wird. Ein Client, der Updates nur empfängt und niemals in derselben Verbindung Daten zurücksendet, profitiert von der Einfachheit von SSE, ohne WebSocket-spezifische Fallstricke wie Ping-Pong-Frames oder eigene Subprotokoll-Verhandlung managen zu müssen.

2. Server-Sent Events Grundlagen: EventSource, HTTP/1.1 vs. HTTP/2

Server-Sent Events basieren auf einer einfachen HTTP-Response mit dem Content-Type text/event-stream, die offen bleibt und kontinuierlich neue, durch Leerzeilen getrennte Textblöcke sendet. Der Browser stellt über die native EventSource-API eine Verbindung her und liefert jedes neue Event als JavaScript-Event aus, ganz ohne externe Bibliothek. Diese Einfachheit ist der Kern dessen, was Server-Sent Events als GraphQL Subscriptions Alternative attraktiv macht.


# Raw shape of an SSE stream, sent as plain text over an open HTTP connection
data: {"type":"next","payload":{"data":{"orderStatusChanged":{"status":"SHIPPED"}}}}

data: {"type":"next","payload":{"data":{"orderStatusChanged":{"status":"DELIVERED"}}}}

data: {"type":"complete"}

Ein wichtiges technisches Detail betrifft die Verbindungslimits von HTTP/1.1: Browser erlauben pro Domain nur sechs gleichzeitige HTTP/1.1-Verbindungen, was bei mehreren offenen SSE-Streams zum Problem werden kann. HTTP/2 hebt dieses Limit durch Multiplexing über eine einzige TCP-Verbindung praktisch auf, weshalb produktive SSE-Deployments idealerweise auf HTTP/2-fähige Server und Proxys setzen sollten.

3. SSE mit GraphQL kombinieren: das graphql-sse-Protokoll

Die Bibliothek graphql-sse definiert ein standardisiertes Protokoll, das GraphQL-Subscription-Semantik über Server-Sent Events transportiert. Statt eines proprietären Formats folgt graphql-sse denselben Grundprinzipien wie graphql-ws, nur eben über den SSE-Transport statt über WebSocket. Das erleichtert die Migration erheblich, weil Resolver-Logik für Subscriptions größtenteils unverändert bleiben kann.


# schema.graphql — subscription resolvers work the same regardless of transport
type Subscription {
  orderStatusChanged(orderId: ID!): Order!
}

type Order {
  id: ID!
  status: OrderStatus!
  updatedAt: String!
}

Der entscheidende Unterschied liegt nicht im Schema, sondern im Transport-Layer darunter. Während graphql-ws eine persistente, bidirektionale WebSocket-Verbindung mit eigenem Subprotokoll für Verbindungsaufbau, Ping/Pong und Subscription-Management aufbaut, nutzt graphql-sse stattdessen eine einfache HTTP-GET-Verbindung, die offenbleibt und Events als Text-Stream sendet. Für Server-Implementierungen, die bereits klassische HTTP-Middleware nutzen, ist das oft der pragmatischere Integrationsweg.

4. Server-Implementierung: SSE-Endpunkt neben dem GraphQL-Endpunkt

Ein SSE-basierter Subscription-Endpunkt läuft typischerweise parallel zum normalen GraphQL-Query/Mutation-Endpunkt, meist unter einem eigenen Pfad wie /graphql/stream. Das graphql-sse-Paket stellt einen fertigen Handler bereit, der sich in gängige Node.js-HTTP-Frameworks einhängen lässt.


// sse-endpoint.ts — mounting a GraphQL SSE handler alongside the regular endpoint
import { createHandler } from 'graphql-sse/lib/use/http'
import { schema } from './schema'
import http from 'node:http'

const sseHandler = createHandler({ schema })

const server = http.createServer((req, res) => {
  if (req.url?.startsWith('/graphql/stream')) {
    // SSE handler manages the text/event-stream response for subscriptions
    sseHandler(req, res)
    return
  }

  // Regular query/mutation endpoint handled elsewhere
  handleRegularGraphQL(req, res)
})

server.listen(4000)

Der Resolver für orderStatusChanged bleibt unverändert ein AsyncIterator, üblicherweise gespeist über PubSub oder ein Redis-Channel. Der Wechsel des Transports von WebSocket zu SSE betrifft also nur die Verbindungsebene, nicht die eigentliche Business-Logik, die Events erzeugt.

5. Client-Integration: EventSource statt WebSocket-Client

Auf Client-Seite ersetzt die native EventSource-API, kombiniert mit dem graphql-sse-Client-Paket, den sonst nötigen WebSocket-Client. Für React-Anwendungen bedeutet das einen deutlich schlankeren Hook, ohne Abhängigkeit von einer WebSocket-Bibliothek wie subscriptions-transport-ws oder graphql-ws.


// use-order-status.ts — subscribing via graphql-sse instead of a WebSocket client
import { createClient } from 'graphql-sse'
import { useEffect, useState } from 'react'

const client = createClient({ url: '/graphql/stream' })

function useOrderStatus(orderId: string) {
  const [status, setStatus] = useState<string | null>(null)

  useEffect(() => {
    const unsubscribe = client.subscribe(
      {
        query: `subscription OnStatus($id: ID!) {
          orderStatusChanged(orderId: $id) { status }
        }`,
        variables: { id: orderId },
      },
      {
        next: (data) => setStatus(data.data?.orderStatusChanged?.status ?? null),
        error: (err) => console.error('SSE subscription error', err),
        complete: () => console.log('Subscription completed'),
      }
    )

    return () => unsubscribe()
  }, [orderId])

  return status
}

Für Teams, die keine zusätzliche Abstraktion über graphql-sse wollen, ist auch eine direkte EventSource-Nutzung ohne GraphQL-spezifisches Client-Paket möglich, sofern der Server rohe JSON-Payloads statt des vollen graphql-sse-Protokolls sendet. Das reduziert Abhängigkeiten weiter, opfert aber die standardisierte Subscription-Semantik.

6. Reconnect, Last-Event-ID und Zuverlässigkeit

Ein oft übersehener Vorteil von Server-Sent Events ist der eingebaute, automatische Reconnect-Mechanismus des Browsers. Bricht die Verbindung ab, versucht EventSource von selbst, sich erneut zu verbinden, ganz ohne eigene Retry-Logik im Anwendungscode. WebSocket-Clients müssen diesen Mechanismus dagegen manuell implementieren, inklusive Backoff-Strategie und Zustandswiederherstellung.

Zusätzlich unterstützt SSE über den id-Feld-Mechanismus und den Last-Event-ID-Header eine eingebaute Wiederherstellung verpasster Events: Sendet der Server jedes Event mit einer fortlaufenden ID, kann der Client nach einem Verbindungsabbruch automatisch die letzte bekannte ID mitsenden, und der Server liefert nur die seither verpassten Events nach. Für GraphQL Subscriptions Alternative-Implementierungen mit graphql-sse ist dieser Mechanismus bereits eingebaut und muss nicht manuell nachgebaut werden.

7. Skalierung: SSE hinter Load-Balancern und Proxies

Lang laufende SSE-Verbindungen stellen ähnliche Anforderungen an Infrastruktur wie WebSockets: Load-Balancer und Reverse-Proxys müssen Timeouts für offene Verbindungen ausreichend hoch setzen, sonst werden Streams vorzeitig gekappt. Nginx benötigt beispielsweise proxy_buffering off und einen erhöhten proxy_read_timeout, damit SSE-Antworten nicht gepuffert und die Verbindung nicht zu früh geschlossen wird.

Ein praktischer Vorteil gegenüber WebSockets bleibt bestehen: Da SSE über gewöhnliches HTTP läuft, funktioniert es meist ohne zusätzliche Firewall- oder Proxy-Konfiguration, während WebSocket-Verbindungen in restriktiven Unternehmensnetzen gelegentlich blockiert werden. Für horizontale Skalierung über mehrere Server-Instanzen hinweg braucht auch SSE einen zentralen Message-Broker wie Redis Pub/Sub, damit ein Event, das auf Instanz A entsteht, auch Clients erreicht, die mit Instanz B verbunden sind.

8. Grenzen von SSE: wann WebSockets doch nötig sind

Server-Sent Events sind bewusst unidirektional, ein Client kann über dieselbe Verbindung keine Daten zum Server senden. Für Anwendungsfälle mit echter bidirektionaler Kommunikation, etwa kollaborative Editoren, in denen mehrere Nutzer gleichzeitig Änderungen senden und empfangen, oder Multiplayer-Spiele mit niedriger Latenzanforderung in beide Richtungen, bleibt WebSocket die richtige Wahl.

Ein weiterer Punkt betrifft Binärdaten: SSE transportiert ausschließlich UTF-8-Text, während WebSocket auch binäre Frames unterstützt. Für GraphQL-Subscriptions, die reine JSON-Payloads liefern, ist das selten ein echtes Problem, für Anwendungen mit Audio- oder Video-Streaming über denselben Kanal jedoch schon. Die Entscheidung für Server-Sent Events als GraphQL Subscriptions Alternative sollte deshalb immer anhand der tatsächlichen Kommunikationsrichtung getroffen werden, nicht pauschal.

9. SSE vs. WebSocket-Subscriptions im Vergleich

Die Wahl zwischen SSE und WebSocket für GraphQL-Echtzeit-Updates hängt von konkreten technischen Anforderungen ab, nicht von persönlicher Präferenz.

Kriterium Server-Sent Events WebSocket-Subscriptions
Kommunikationsrichtung Nur Server zu Client Bidirektional
Automatischer Reconnect Ja, im Browser eingebaut Muss manuell implementiert werden
Firewall-Kompatibilität Sehr gut, reines HTTP Gelegentlich blockiert
Binärdaten Nicht unterstützt Unterstützt
Setup-Komplexität Gering Höher, eigenes Subprotokoll

Für die meisten GraphQL-Subscription-Anwendungsfälle, die lediglich Server-zu-Client-Updates liefern, ist Server-Sent Events als GraphQL Subscriptions Alternative die einfachere und robustere Wahl. Erst bei echtem bidirektionalem Bedarf oder Binärdaten-Transport lohnt sich der zusätzliche Aufwand von WebSocket-Subscriptions.

Mironsoft

GraphQL-Echtzeit-Architektur, SSE- und WebSocket-Integration

WebSocket-Subscriptions fühlen sich überdimensioniert an?

Wir prüfen, ob eure GraphQL-Echtzeit-Updates auch mit Server-Sent Events funktionieren, und implementieren eine schlankere Alternative ohne eigenes WebSocket-Protokoll.

Architektur-Review

Prüfen, ob eure Subscriptions wirklich bidirektionale Kommunikation brauchen

graphql-sse-Migration

Umstieg von WebSocket-Subscriptions auf einen schlankeren SSE-Transport

Skalierung

Load-Balancer- und Proxy-Konfiguration für stabile SSE-Verbindungen

10. Zusammenfassung

Server-Sent Events als GraphQL Subscriptions Alternative passen genau auf den häufigsten Echtzeit-Anwendungsfall: unidirektionale Updates vom Server zum Client. Das graphql-sse-Protokoll überträgt dieselbe Subscription-Semantik wie graphql-ws, nur über einen einfacheren HTTP-basierten Transport mit eingebautem Reconnect und weniger Infrastrukturkomplexität. Die Client-Integration über EventSource ist schlanker als ein WebSocket-Client und braucht keine manuelle Retry-Logik.

Die Grenzen von SSE sind klar: keine bidirektionale Kommunikation, kein nativer Binärdaten-Transport. Für die überwiegende Mehrheit der GraphQL-Subscription-Anwendungsfälle, von Bestellstatus-Updates bis zu Live-Dashboards, reicht die unidirektionale Natur von Server-Sent Events jedoch vollkommen aus und spart erheblichen Implementierungsaufwand gegenüber WebSocket-basierten Subscriptions.

Server-Sent Events als GraphQL Subscriptions Alternative — Das Wichtigste auf einen Blick

Unidirektional passt meist

Die meisten Subscriptions liefern nur Server-zu-Client-Updates, genau SSEs Stärke.

graphql-sse-Protokoll

Gleiche Subscription-Semantik wie graphql-ws, nur über HTTP statt WebSocket.

Eingebauter Reconnect

EventSource verbindet sich automatisch neu, ohne eigene Retry-Logik.

Grenzen kennen

Bei echter Bidirektionalität oder Binärdaten bleibt WebSocket die richtige Wahl.

11. FAQ: GraphQL und Server-Sent Events

1Offizieller Ersatz für Subscriptions?
Kein offizielles Ersatzprotokoll, aber graphql-sse implementiert dieselbe Semantik als praktikable Alternative zu graphql-ws.
2Muss der Resolver-Code angepasst werden?
Nein, Resolver bleiben AsyncIterators, nur die Transport-Ebene ändert sich, nicht die Business-Logik.
3Funktioniert EventSource überall?
Ja, Teil des HTML-Standards, nativ in allen aktuellen Browsern ohne Polyfill.
4Wie werden verpasste Events nachgeliefert?
Über den Last-Event-ID-Header, den der Browser automatisch mitsendet, wenn Events eine id tragen.
5Braucht es einen Message-Broker?
Ja, wie bei WebSocket-Subscriptions wird ein zentraler Broker wie Redis Pub/Sub bei mehreren Instanzen benötigt.
6Kann SSE Binärdaten übertragen?
Nein, nur UTF-8-Text. Für Binärdaten-Streaming bleibt WebSocket die richtige Wahl.
7Ist SSE genauso latenzarm?
Ja, für unidirektionale Updates praktisch identisch, der Unterschied liegt im Verbindungsaufbau, nicht in der Übertragung.
8Wie viele Verbindungen unterstützt ein Browser?
Sechs pro Domain unter HTTP/1.1, unter HTTP/2 effektiv aufgehoben durch Multiplexing.
9Welche Nginx-Konfiguration ist nötig?
proxy_buffering off und ein ausreichend hoher proxy_read_timeout gegen vorzeitiges Schließen.
10Wann lohnt sich WebSocket statt SSE?
Bei echter Bidirektionalität wie kollaborativen Editoren, oder bei Binärdaten über denselben Kanal.