DEV Community

Cover image for Event Lifecycle Control in GraphQL Subscriptions
Stefan  🚀
Stefan 🚀

Posted on

Event Lifecycle Control in GraphQL Subscriptions

Cosmo Streams adds three handlers to the router that control subscription behavior: authorization when a client subscribes, per-subscriber filtering while events flow, and validation when mutations publish events. Instead of building custom subscription infrastructure, you write Go functions that run at specific points in the event lifecycle.

What EDFS Already Solved

EDFS connects your message broker to GraphQL subscriptions. The router listens for events and publishes them as subscriptions. This remains the foundation of how Cosmo Streams operates.

Cosmo Streams extends that by moving subscription authorization, event filtering, and mutation validation into the router itself. Three handlers cover three stages: when a subscription starts, while events are in flight, and when mutations publish events.

Authorization Was All or Nothing

EDFS gave you binary access: authenticated users could subscribe to any topic. There was no way to scope access based on roles, user attributes, or subscription context.

Teams worked around this by building custom services, adding resolver checks, or processing events after delivery. That meant managing connection state, duplicating auth logic, and manually fanning out events.

SubscriptionOnStart Handler

The SubscriptionOnStart handler runs when a client subscribes. You have access to JWT data, HTTP headers, connection details, and GraphQL variables. Use this context to allow or deny the subscription.

Example: users subscribe to order updates. The handler validates the JWT and issuer before allowing the subscription. Once established, the OnReceiveEvent handler filters which events each subscriber receives based on permissions.

Two users subscribe to the same GraphQL subscription but see different events. Per-subscriber filtering wasn't possible with EDFS alone.

New Subscribers Started from Zero

EDFS is purely event-driven. Subscribe, then receive future events as they happen. Efficient, but creates UX issues.

A user joins a live soccer match stream. Score is already 1-0. With EDFS, they see nothing until the next goal, which might be 30 minutes away. The client is connected but has no context.

Initial State via SubscriptionOnStart

The same handler that handles authorization can send data immediately when a subscription starts.

A client subscribes to an in-progress match. The router queries the current score, sends it immediately, and then continues streaming future goals. The client gets the initial state, then the event stream.

One handler, two problems: access control and initial data delivery.

Everyone Got Identical Events

EDFS fanned out events to all subscribers identically. Simple, performant, but inflexible. Some teams needed to filter or modify events per subscriber.

Two users subscribe to the same topic. One's an admin who should see everything. The other's a regular user who should only see their own data.

OnReceiveEvent Handler

OnReceiveEvent runs when an event arrives from the broker, before the router delivers it. Executes once per subscriber, enabling per-subscriber decisions about delivery.

Example: user subscribes to order updates, receives only events from orders they created. Handler filters based on customer ID from the token matched against customer ID in the event.

This filtering happens for each subscriber. 30,000 subscribers means 30,000 handler executions. Router handles these asynchronously with configurable concurrency limits.

Mutation Side Validation

OnPublishEvent runs when a mutation publishes an event through Cosmo Streams, before the router emits to the message system. Validates or enriches data before it enters your event infrastructure.

Example: user creates an order via mutation. Handler validates the user is creating an order for themselves, not another user. Matches customer ID from token against customer ID in mutation input.

Without this, router-level validation was difficult. EDFS just transformed mutation data into events and sent them out. Now you have a validation layer before events enter your system.

Implementation

Handlers are implemented through the Custom Module system. Write logic in Go as modules that compile with the router.

Three extension points:

  • SubscriptionOnStart - when subscriptions start
  • OnReceiveEvent - when events arrive from broker
  • OnPublishEvent - when mutations publish events

All handlers are optional. Without them, router falls back to standard EDFS behavior.

Performance

OnReceiveEvent processes every subscriber individually. 30,000 subscribers = 30,000 handler runs per event batch.

Don't make API calls per handler run. That's 30K external calls per event with 30K subscribers. Use in-process or external caches. Custom Modules are Go functions, so you can run async routines and look up pre-computed data instead of hitting upstream systems.

Handler logic must be efficient. The router handles concurrency and memory management, but blocking operations at this scale will cause problems.

Getting Started

The demo repository shows all three handlers with a working order system. See how SubscriptionOnStart validates JWTs, how OnPublishEvent ensures users only create their own orders, and how OnReceiveEvent filters events so customers see only their own orders.

Cosmo Streams turns GraphQL subscriptions from a broadcast pipe into a policy-aware event system with control at subscribe time, in-flight, and at publish time.


Adapted from the original article on the WunderGraph blog.

Top comments (0)