Multipart HTTP protocol for GraphQL Subscriptions

Enable clients to receive real-time updates via multipart HTTP protocol


To execute GraphQL subscription operations on the GraphOS Router, client apps do not communicate over WebSocket. Instead, they use HTTP with multipart responses. This multipart protocol is built on the same Incremental Delivery over HTTP spec that the GraphOS Router uses to support the @defer directive.

Use this reference if you're adding protocol support to a new GraphQL client library. Apollo Client, Apollo Kotlin, and Apollo iOS all support this protocol. Apollo Client also provides network adapters for the Relay and urql libraries.

Executing a subscription

To execute a subscription on the GraphOS Router, a GraphQL client sends an HTTP request with almost the exact same format that it uses for query and mutation requests.

The only difference is that the request should include the following Accept header:

Text
Example header
1Accept: multipart/mixed;subscriptionSpec="1.0", application/json
tip
The value for boundary should always be graphql, and the value for subscriptionSpec should always be 1.0.

As subscription events occur, the router sends back HTTP response "parts" that conform to the definition of multipart content specified in RFC1341.

An example response might look like this:

Text
1--graphql
2Content-Type: application/json
3
4{}
5--graphql
6Content-Type: application/json
7
8{"payload": {"data": { "newPost": { "id": 123, "title": "Hello!"}}}}
9--graphql--
  • If the request uses HTTP/1, the response includes the Transfer-Encoding: chunked header.

  • If the request uses HTTP/2 (which provides built-in support for data streaming), chunked encoding is not used (and is in fact disallowed).

Heartbeats

While a client subscription remains active, the GraphOS Router sends periodic "heartbeat" response parts to prevent any intermediaries from closing the connection. The body of a heartbeat is an empty JSON object, which clients should ignore silently:

Text
Heartbeat response part
1--graphql
2Content-Type: application/json
3
4{}
5--graphql--

Message and error format

This protocol differentiates between transport-level errors and GraphQL errors in response payloads themselves. This is because the GraphQL response format is defined in the GraphQL spec, and unexpected fields might be confusing or could even break client typing.

With the exception of heartbeats, every response part body includes a payload property, which contains standard GraphQL response properties. The payload property can be null if a transport-level error occurs.

If a GraphQL-level error occurs, the GraphOS Router can sometimes still return partial data, and the subscription connection should remain open. These errors are provided within the payload property:

JSON
1{
2  "payload": {
3    "errors": [...],
4    "data": {...},
5    "extensions": {...}
6  }
7}

If a fatal transport-level error occurs, the router sends a message with a top-level errors field and null payload field, then closes the connection:

JSON
1{
2  "payload": null,
3  "errors": [...]
4}

Both types of errors follow the GraphQL error format, but top-level errors never include locations or path.

Additional resources

Check out the federated subscriptions course to explore an end-to-end implementation with Apollo Router, Apollo Server, and Typescript. You can also see the Apollo Solutions federated subscriptions repository for an example of federated subscriptions via an HTTP Multipart based subscription with the router in HTTP callback mode.

note
The code in this repository is experimental and has been provided for reference purposes only.
Feedback

Edit on GitHub

Forums