Serve
Features
Subscriptions

Subscriptions

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

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

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.

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 Mesh Compose or Apollo Rover, simply run Mesh Serve without any additional configuration!

mesh-serve --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
    }
  }
}

Mesh 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"}'

Subgraphs using WebSockets

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

We’ll install @graphql-mesh/transport-ws to add WebSockets support.

npm i @graphql-mesh/transport-ws

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

mesh.config.ts
import { defineConfig } from '@graphql-mesh/serve-cli'
import type { WSTransportOptions } from '@graphql-mesh/transport-http'
 
export const serveConfig = 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 Mesh Serve with:

mesh-serve

Downstream clients are still subscribing to Mesh Serve gateway through any supported subscriptions protocol, but upstream Mesh Serve will use long-living WebSocket connections to the “products” service.

Authorization header

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

mesh.config.ts
import { defineConfig } from '@graphql-mesh/serve-cli'
import type { WSTransportOptions } from '@graphql-mesh/transport-ws'
 
export const serveConfig = 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.

Subscriptions using HTTP Callback

If your subgraph uses HTTP Callback protocol for subscriptions, Mesh Serve will need additional configuration.

We’ll install @graphql-mesh/transport-http-callback to add HTTP Callback support.

npm i @graphql-mesh/transport-http-callback
mesh.config.ts
import { defineConfig, PubSub, useWebhooks } from '@graphql-mesh/serve-cli'
import type { HTTPCallbackTransportOptions } from '@graphql-mesh/transport-http-callback'
 
export const serveConfig = defineConfig({
  supergraph: 'supergraph.graphql',
  // Setup a PubSub engine to handle webhooks between different instances of Mesh for scalability
  pubsub: new PubSub(),
  // Setup Mesh to listen for webhook callbacks, and emit the payloads through PubSub engine
  plugins: ctx => [useWebhooks()],
  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
        }
      }
    }
  }
})

Closing active subscriptions on schema change

When the schema changes in Mesh, 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.