---
searchable: false
---

import { Callout } from '@theguild/components'

# Subscriptions

Hive Gateway fully supports federated subscriptions and behaves just like
[Federation GraphQL subscriptions in Apollo Router](https://www.apollographql.com/docs/router/executing-operations/subscription-support).

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

- [GraphQL over SSE](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverSSE.md)
- [GraphQL over WebSocket](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverWebSocket.md)
- [HTTP Callback](https://www.apollographql.com/docs/router/executing-operations/subscription-callback-protocol/)

Clients connecting to the Hive Gateway must use the:

- [GraphQL over SSE](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverSSE.md)

## Example

We'll implement two
[GraphQL Yoga federation services](https://the-guild.dev/graphql/yoga-server/docs/features/apollo-federation#federation-service)
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](https://www.apollographql.com/docs/router/executing-operations/subscription-support/#example-execution),
except for that we use GraphQL Yoga here and significantly reduce the setup requirements.

You can find this example
[source on GitHub](https://github.com/ardatan/graphql-mesh/tree/master/e2e/federation-subscriptions-passthrough).

### Install dependencies

```ssh npm2yarn
npm i graphql-yoga @apollo/subgraph graphql
```

### Products service

```ts filename="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

```ts filename="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](https://graphql-mesh.com/) or
[Apollo Rover](https://www.apollographql.com/docs/rover/), simply run Hive Gateway without any
additional configuration!

```sh
hive-gateway supergraph supergraph.graphql
```

### Subscribe

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

```graphql
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](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) or
[graphql-sse](https://the-guild.dev/graphql/sse)). For the sake of brevity, we'll subscribe using
`curl`:

```sh
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](https://www.apollographql.com/docs/apollo-server/data/subscriptions/)),
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:

```ts filename="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:

```sh
hive-gateway supergraph
```

Downstream clients are subscribing to Hive Gateway gateway through the
[GraphQL over SSE protocol](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverSSE.md),
but upstream Hive Gateway will use long-living WebSocket connections to the "products" service.

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

### Propagation of authorization

Hive Gateway can propagate the downstream client's `Authorization` header contents to the upstream
WebSocket connections through the
[`ConnectionInit` message payload](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverWebSocket.md#connectioninit).

<Callout type="warning">
  If either `connectionParams` or `headers` are used with dynamic values, it can drastically
  increase the number of upstream WebSockets connections. <br />
  Since `headers` and `connectionParams` can only be applied at connection time, a new connection is
  required for each different set of values provided.
</Callout>

```ts filename="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:

```json
{
  "connectionParams": {
    "token": "<CONTENTS_OF_AUTHORIZATION_HEADER>"
  }
}
```

<Callout>
  This is also what Apollo Router when [propagating auth on
  WebSockets](https://www.apollographql.com/docs/router/executing-operations/subscription-support/#websocket-auth-support).
</Callout>

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

```ts filename="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}']]
        }
      }
    }
  }
})
```

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

## Subscriptions using HTTP Callback

If your subgraph uses
[HTTP Callback protocol for subscriptions](https://www.apollographql.com/docs/router/executing-operations/subscription-callback-protocol/),
Hive Gateway will need additional configuration.

```ts filename="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:

```ts filename="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:

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

<Callout>
  This is also what Apollo Router when [terminating subscriptions on schema
  update](https://www.apollographql.com/docs/router/executing-operations/subscription-support/#termination-on-schema-update).
</Callout>
