Subscriptions

Hive Gateway fully supports federated subscriptions and behaves just like Federation GraphQL subscriptions in Apollo Router.

Subgraphs providing subscriptions can communicate with Hive Gateway through one of the following protocols:

Clients connecting to the Hive Gateway must use the:

Example

We’ll implement two GraphQL Yoga federation services behaving as subgraphs. The “products” service exposes a subscription operation type for subscribing to product changes, while the “reviews” service simply exposes review stats about products.

The example is somewhat similar to Apollo’s documentation, except for that we use GraphQL Yoga here and significantly reduce the setup requirements.

You can find this example source on GitHub.

Install dependencies

npm i graphql-yoga @apollo/subgraph graphql

Products service

products.ts
import { createServer } from 'http'
import { parse } from 'graphql'
import { createYoga } from 'graphql-yoga'
import { buildSubgraphSchema } from '@apollo/subgraph'
import { resolvers } from './my-resolvers'
 
const typeDefs = parse(/* GraphQL */ `
  type Product @key(fields: "id") {
    id: ID!
    name: String!
    price: Int!
  }
 
  type Subscription {
    productPriceChanged: Product!
  }
`)
 
const yoga = createYoga({ schema: buildSubgraphSchema([{ typeDefs, resolvers }]) })
 
const server = createServer(yoga)
 
server.listen(40001, () => {
  console.log('Products subgraph ready at http://localhost:40001')
})

Reviews service

reviews.ts
import { createServer } from 'http'
import { parse } from 'graphql'
import { createYoga } from 'graphql-yoga'
import { buildSubgraphSchema } from '@apollo/subgraph'
import { resolvers } from './my-resolvers'
 
const typeDefs = parse(/* GraphQL */ `
  extend type Product @key(fields: "id") {
    id: ID! @external
    reviews: [Review!]!
  }
 
  type Review {
    score: Int!
  }
`)
 
const yoga = createYoga({ schema: buildSubgraphSchema([{ typeDefs, resolvers }]) })
 
const server = createServer(yoga)
 
server.listen(40002, () => {
  console.log('Reviews subgraph ready at http://localhost:40002')
})

Start Gateway

After having generated a supergraph file supergraph.graphql for the two subgraphs, either using GraphQL Mesh or Apollo Rover, simply run Hive Gateway without any additional configuration!

hive-gateway supergraph supergraph.graphql

Subscribe

Let’s now subscribe to the product price changes by executing the following query:

subscription {
  productPriceChanged {
    # Defined in Products subgraph
    name
    price
    reviews {
      # Defined in Reviews subgraph
      score
    }
  }
}

Hive Gateway will inteligently resolve all fields on subscription events and deliver you the complete result.

You can subscribe to the gateway through Server-Sent Events (SSE) (in JavaScript, using EventSource or graphql-sse). For the sake of brevity, we’ll subscribe using curl:

curl 'http://localhost:4000/graphql' \
  -H 'accept: text/event-stream' \
  -H 'content-type: application/json' \
  --data-raw '{"query":"subscription OnProductPriceChanged { productPriceChanged { name price reviews { score } } }","operationName":"OnProductPriceChanged"}'

Subscriptions using WebSockets

If your subgraph uses WebSockets for subscriptions support (like with Apollo Server), Hive Gateway will need additional configuration pointing to the WebSocket server path on the subgraph.

And configure Hive Gateway to use the /subscriptions path on the “products” subgraph for WebSocket connections:

gateway.config.ts
import { defineConfig, type WSTransportOptions } from '@graphql-hive/gateway'
 
export const gatewayConfig = defineConfig({
  supergraph: 'supergraph.graphql',
  transportEntries: {
    // use "*.http" to apply options to all subgraphs with HTTP
    '*.http': {
      options: {
        subscriptions: {
          kind: 'ws',
          location: '/subscriptions'
        } satisfies WSTransportOptions
      }
    }
  }
})

Now simply start Hive Gateway with:

hive-gateway supergraph

Downstream clients are subscribing to Hive Gateway gateway through the GraphQL over SSE protocol, but upstream Hive Gateway will use long-living WebSocket connections to the “products” service.

💡

WebSocket for communications between Hive Gateway and subgraphs are suboptimal compared to other possible transports. We recommend using either SSE or HTTP Callbacks instead.

Propagation of authorization

Hive Gateway can propagate the downstream client’s Authorization header contents to the upstream WebSocket connections through the ConnectionInit message payload.

⚠️

If either connectionParams or headers are used with dynamic values, it can drastically increase the number of upstream WebSockets connections.
Since headers and connectionParams can only be applied at connection time, a new connection is required for each different set of values provided.

gateway.config.ts
import { defineConfig, type WSTransportOptions } from '@graphql-hive/gateway'
 
export const gatewayConfig = defineConfig({
  supergraph: 'supergraph.graphql',
  transportEntries: {
    // use "*.http" to apply options to all subgraphs with HTTP
    '*.http': {
      options: {
        subscriptions: {
          kind: 'ws',
          location: '/subscriptions',
          options: {
            connectionParams: {
              token: '{context.headers.authorization}'
            }
          } satisfies WSTransportOptions
        }
      }
    }
  }
})

The contents of the payload will be available in graphql-ws connectionParams:

{
  "connectionParams": {
    "token": "<CONTENTS_OF_AUTHORIZATION_HEADER>"
  }
}
💡

This is also what Apollo Router when propagating auth on WebSockets.

It is also possible, but not recommended, to propagate HTTP headers by sending them alongside the WebSocket upgrade request.

gateway.config.ts
import { defineConfig, type WSTransportOptions } from '@graphql-hive/gateway'
 
export const gatewayConfig = defineConfig({
  supergraph: 'supergraph.graphql',
  transportEntries: {
    // use "*.http" to apply options to all subgraphs with HTTP
    '*.http': {
      options: {
        subscriptions: {
          kind: 'ws',
          location: '/subscriptions',
          headers: [['authorization', '{context.headers.authorization}']]
        }
      }
    }
  }
})
💡

The headers will be sent only with the upgrade request. They will not be sent again during the lifecycle of the subscription.

Subscriptions using HTTP Callback

If your subgraph uses HTTP Callback protocol for subscriptions, Hive Gateway will need additional configuration.

gateway.config.ts
import { defineConfig, type HTTPCallbackTransportOptions } from '@graphql-hive/gateway'
 
export const gatewayConfig = defineConfig({
  supergraph: 'supergraph.graphql',
  // Setup Hive Gateway to listen for webhook callbacks, and emit the payloads through PubSub engine
  webhooks: true,
  transportEntries: {
    // use "*.http" to apply options to all subgraphs with HTTP
    '*.http': {
      options: {
        subscriptions: {
          kind: 'http-callback',
          options: {
            // The gateway's public URL, which your subgraphs access, must include the path configured on the gateway.
            public_url: 'http://localhost:4000/callback',
            // The path of the router's callback endpoint
            path: '/callback',
            // Heartbeat interval to make sure the subgraph is still alive, and avoid hanging requests
            heartbeat_interval: 5000
          } satisfies HTTPCallbackTransportOptions
        }
      }
    }
  }
})

Subscriptions transport configuration

By default, subscriptions will use the same transport than queries and mutation. This can be change using the transportEntries option.

The key of each entry determine which subgraph will be impacted:

  • *: all subgraphs
  • *.{transportKind}: all subgraphs using transportKind. For example, *.http will impact all subgraph using the http transport.
  • {subgraphName}: a specific subgraph.

Configuration are inherited and merged from the least specific to the most specific matcher. Only exception is the headers which is not inherited for the ws transport.

Example

Let be 4 subgraphs:

  • products: using http transport for queries, and HTTP callbacks for subscriptions
  • views: using http transport for queries, and WS for subscriptions
  • stocks: using http transport for queries, and WS for subscriptions
  • stores: using mysql transport

The configuration will be:

gateway.config.ts
import { defineConfig, type HTTPCallbackTransportOptions } from '@graphql-hive/gateway'
 
export const gatewayConfig = defineConfig({
  transportEntries: {
    '*.http': {
      // Will be applied to products, views and stocks subgraphs, but not stores.
      options: {
        subscriptions: {
          kind: 'ws',
          options: {
            connectionParams: {
              token: '{context.headers.authorization}'
            }
          }
        }
      }
    },
    products: {
      // Will override the subscriptions configuration for products subgraph only
      options: {
        subscriptions: {
          kind: 'http-callback',
          location: '/subscriptions',
          headers: [['authorization', 'context.headers.authorization']]
        }
      }
    }
  }
})

Closing active subscriptions on schema change

When the schema changes in Hive Gateway, all active subscriptions will be completed after emitting the following execution error:

{
  "errors": [
    {
      "message": "subscription has been closed due to a schema reload",
      "extensions": {
        "code": "SUBSCRIPTION_SCHEMA_RELOAD"
      }
    }
  ]
}
💡

This is also what Apollo Router when terminating subscriptions on schema update.