Proposal: support X-GraphQL-Event-Stream header
Server-sent events are a perfect mechanism for informing a GraphQL client, such as graphql-playground, that the schema has been updated. They don't require the complexity of websockets, and they are uni-directional.
I'm proposing that when introspecting a GraphQL API, if the header X-GraphQL-Event-Stream is detected then GraphQL Playground should subscribe to the text/event-stream at that (relative or absolute) URL and when it receives the change event it should automatically re-introspect the GraphQL schema.
Not much code should be required to implement this, just something like:
const streamUrl = response.headers["x-graphql-event-stream"];
if (streamUrl) {
const endpointUrl = new URL(endpoint);
const streamUrl = new URL(streamUrl, endpointUrl);
if (endpointUrl.host !== streamUrl.host) {
throw new Error(
`Stream and endpoint hosts don't match - '${streamUrl.host}' !== '${endpointUrl.host}'`
);
}
const eventSource = new EventSource(streamUrl);
eventSource.addEventListener("change", this.refreshSchema, false);
eventSource.addEventListener("open", () => { /* ... */ }, false);
eventSource.addEventListener("error", () => { /* ... */ }, false);
}
Would love to hear your thoughts.
This sounds good! That would also be a pretty simple first task. Are you already using the X-GraphQL-Event-Stream header somewhere else or would we introduce it for this purpose?
I’ve added it to PostGraphile and have an open PR with GraphiQL.app; happy to open a PR here too but it might take me a while to get around to. The code above is a tidied version of the GraphiQL.app PR.
I really like this idea and wonder whether this could also be implemented with graphql-yoga out of the box. There is currently an open PR for hot-reload support: https://github.com/graphcool/graphql-yoga/pull/190
@benjie do you have any ideas how the Playground could pick up whether the underlying GraphQL server has been restarted?
Would detecting the event-stream closing be sufficient?
That sounds like a reasonable solution. So once it's closed it should start pinging the endpoint again like this:

Sounds reasonable. I wrote an exponential backoff with a 30 second max cutoff (basically untested though) for GraphiQL.app:
https://github.com/benjie/graphiql-app/blob/44cfa3a52c1e547b858fcfa655ed1bee3fa7fab8/app/components/App.js#L262-L313
I was only hacking it together for my own purposes at the time; that code needs some serious refactoring.
@benjie I'm working on a SSE Link for Apollo, it uses a single SSE channel for all subscriptions, using a secret token to identify the return channel without cookies. This is the first time I come across your efforts - did anything come of it?
My status: it's working in the ideal state but needs work on re-establishing subscriptions after connection loss or server restart, that kind of thing. Also, I'm trying to get it working with the Playground but hitting #1362
@wmertens This was intended to be a way to indicate schema changes (for hot reloading a GraphQL schema); I don't see an issue with using it also for subscriptions using a different event name though. As it is, it has been implemented in Altair (https://sirmuel.design/a-better-graphql-developer-experience-with-x-graphql-event-stream-1256aef96f24) but I've not tracked its adoption into the rest of the ecosystem. A quick search on sourcegraph.com suggests it's used in PostGraphile, @enisdenjo's graphql-sse, and Altair but no other hits.
graphql-sse does not depend on the X-GraphQL-Event-Stream header in any way. But it could be used for discovery indicating to the client that there's a SSE endpoint available.
Following my brief investigations, trying to integrate graphql-sse in PostGraphile, running both the schema update stream and GraphQL over Server-Sent Events protocol on the same route is no problem at all. In PostGraphile's case, a GET text/event-stream request without any query parameters is considered a schema update subscription; while on the other hand, the SSE protocol needs the GraphQL execution parameters - either in the query params for GETs or in the body for POSTs.