Cosmo Streams doesn’t try to make traditional GraphQL subscriptions less painful. It removes the root cause of the pain altogether.
GraphQL subscriptions are notoriously difficult to build and operate at scale.
In a classic setup, you’re expected to run a dedicated subscription service yourself. That service must keep long-lived WebSocket connections, implement the correct GraphQL subprotocol, manage heartbeats and reconnects, listen for domain events, fetch and compose data from other services, and finally fan out updates to thousands of connected clients — all in real time.
This architecture is inherently stateful. Every active subscription lives somewhere in memory. As traffic grows, so does connection overhead, memory pressure, and operational complexity. Subtle performance issues and edge-case failures are common, especially once you move beyond a few hundred clients.
Federation makes things even harder. Now the router is no longer the only place dealing with subscriptions — your subgraphs must participate too. They often need to hold WebSocket connections, implement subscription loops, or support proprietary callback protocols. This creates tight coupling between your graph architecture and your runtime environment, making serverless deployments difficult or outright impossible.
In short: traditional GraphQL subscriptions push state, complexity, and scalability concerns deep into every layer of your system.
Cosmo Streams takes a fundamentally different approach.
Instead of asking subgraphs to host subscriptions, Cosmo Streams treats subscriptions as an event-driven problem, not a connection-driven one.
Subgraphs remain completely stateless. They don’t manage WebSockets. They don’t track subscriptions. They don’t run subscription loops. When something changes, they simply emit an event to an existing message broker like Apache Kafka, NATS, or Redis — infrastructure most event-driven systems already rely on.
The Cosmo Router listens to those events.
When an event arrives, the router determines which active subscriptions are affected, efficiently deduplicates work, and fetches the required data from subgraphs using plain HTTP requests. The updated results are then broadcast to clients over WebSockets, SSE, or multipart responses — all fully managed at the gateway layer.
This shift has important consequences:
-
Stateless subgraphs Subgraphs don’t hold connections or subscription state, making them ideal for Lambda, Cloud Run, or any serverless environment.
-
Centralized connection management All client connections live in the router, where they can be optimized, monitored, and scaled efficiently.
-
Event-native architecture Subscriptions integrate naturally with Kafka, NATS, or Redis instead of introducing yet another stateful protocol.
-
Better performance at scale The router deduplicates subscriptions, minimizes fetches, and uses highly efficient I/O primitives to handle tens of thousands of concurrent clients with low memory overhead.
-
Zero subscription logic in subgraphs No WebSocket servers, no callback protocols, no proprietary APIs — just HTTP.
This self-contained demo deploys three GraphQL subgraphs: Customers, Products, and Orders.
- The Customers subgraph defines two customers: John Doe and Jane Doe.
- The Products subgraph exposes three products that customers can order.
- The Orders subgraph stores all orders created by customers, including their selected products.
None of these subgraphs serve subscriptions or mutations. On their api they can only be queried.
The demo also spawns a Cosmo Router. The router configures a special fourth subgraph, OrderEvents, which is an Event-Driven Graph. It is not backed by any subgraph and exposes:
- the
createOrdermutation - the
orderCreatedsubscription
When createOrder is called, the router emits an orderRequested event to NATS.
The Orders subgraph receives this event, stores the order and publishes an orderCreated event.
The router then resolves the client requests and sends it to all subscribed clients.
Note
The Orders subgraph does not expose mutations. It stores orders exclusively through events.
A NATS server powers the event delivery layer.
flowchart LR
subgraph clients["Clients"]
direction TB
mutator["Mutator Client"]
subscriber["Subscriber Client<br/>(orderCreated subscription)"]
end
subgraph router["Cosmo Router"]
direction TB
routerCore["Router Core<br/>(EDFS)"]
natsConsumer["NATS Consumer"]
end
nats[("NATS Server")]
subgraph subgraphs["Subgraphs"]
direction TB
orders["Orders Subgraph"]
products["Products Subgraph"]
customers["Customers Subgraph"]
end
%% Mutation flow (EDFS publish)
mutator -->|"① createOrder mutation"| routerCore
routerCore -->|"② Publish to orderRequested"| nats
nats -->|"③ Consume orderRequested"| orders
orders -->|"④ Publish to orderCreated"| nats
%% Subscription flow
nats -->|"⑤ Consume orderCreated"| natsConsumer
natsConsumer -->|"⑥ Trigger resolution"| routerCore
routerCore -->|"⑦ Fetch entity data"| orders
routerCore -->|"⑦ Fetch entity data"| products
routerCore -->|"⑦ Fetch entity data"| customers
routerCore -->|"⑧ Push subscription data"| subscriber
The demo also includes custom authentication and filtering logic using Cosmo Streams Custom Modules.
- Clients must provide a valid JWT issued by a trusted authority.
orderRequestedevents are rejected if the JWT’s customer ID does not match the order’s customer ID.- Subscription streams are filtered so that customers only receive events related to their own orders.
-
SubscriptionOnStart
- When: When a client starts a subscription.
- Purpose: Validates the client’s JWT and its issuer.
-
OnPublishEvent
- When: After a
createOrdermutation is invoked but before events are sent to NATS. - Purpose: Ensures the JWT’s customer ID matches the order’s customer ID.
- When: After a
-
OnReceiveEvent
- When: Whenever a new event is received from NATS before resolution.
- Purpose: Filters events based on the client's claims (e.g., customers only receive their own
orderCreatedevents).
These hooks enforce per-user authentication and event filtering for GraphQL subscriptions.
You can the code at router/module/module.go.
For more details, see the
Cosmo Streams Documentation.
You must have Docker Desktop or Docker with Compose installed.
Start all subgraphs, the router, and a NATS server:
docker compose up -dThis command also starts two subscription clients (one for each customer). To view their logs:
docker compose logs -f subscriber-johndoe
docker compose logs -f subscriber-janedoeTo create orders, open another terminal:
source .env
# Create an order for John Doe (customer 1) with product IDs 2 and 3.
docker compose run --rm -e JWT_TOKEN=$JWT_JOHNDOE mutator 1 2 3
# Create an order for Jane Doe (customer 2) with product IDs 1 and 2.
docker compose run --rm -e JWT_TOKEN=$JWT_JANEDOE mutator 2 1 2Only the correct customer’s subscription client will receive each event.
This project is licensed under the MIT License.
See the LICENSE file for details.
