diff --git a/packages/web/docs/src/pages/docs/api-reference/gateway-cli.mdx b/packages/web/docs/src/pages/docs/api-reference/gateway-cli.mdx index 4b4eb1022..9ed955a31 100644 --- a/packages/web/docs/src/pages/docs/api-reference/gateway-cli.mdx +++ b/packages/web/docs/src/pages/docs/api-reference/gateway-cli.mdx @@ -24,31 +24,37 @@ which will print out the following: ``` Usage: hive-gateway [options] [command] -serve GraphQL federated architecture for any API service(s) +Federated GraphQL Gateway Options: - --fork count of workers to spawn. defaults to "os.availableParallelism()" when NODE_ENV is "production", otherwise only one (the main) worker - (default: 1 (env: FORK) - -c, --config-path path to the configuration file. defaults to the following files respectively in the current working directory: gateway.config.ts, - gateway.config.mts, gateway.config.cts, gateway.config.js, gateway.config.mjs, gateway.config.cjs (env: CONFIG_PATH) - -h, --host host to use for serving (default: "127.0.0.1" (default: "127.0.0.1") - -p, --port port to use for serving (default: 4000 (env: PORT) - --polling schema polling interval in human readable duration (default: "10s") (env: POLLING) - --no-masked-errors don't mask unexpected errors in responses - --masked-errors mask unexpected errors in responses (default: true) - --hive-registry-token Hive registry token for usage metrics reporting (env: HIVE_REGISTRY_TOKEN) - --apollo-graph-ref Apollo graph ref of the managed federation graph (@) (env: APOLLO_GRAPH_REF) - --apollo-key Apollo API key to use to authenticate with the managed federation up link (env: APOLLO_KEY) - --help display help for command + --fork count of workers to spawn. uses "12" (available parallelism) workers when NODE_ENV is "production", otherwise "1" (the main) worker (default: 1 (env: FORK) + -c, --config-path path to the configuration file. defaults to the following files respectively in the current working directory: gateway.ts, gateway.mts, gateway.cts, gateway.js, gateway.mjs, gateway.cjs (env: CONFIG_PATH) + -h, --host host to use for serving (default: "0.0.0.0" (default: "0.0.0.0") + -p, --port port to use for serving (default: 4000 (env: PORT) + --polling schema polling interval in human readable duration (default: "10s") (env: POLLING) + --no-masked-errors don't mask unexpected errors in responses + --masked-errors mask unexpected errors in responses (default: true) + --hive-registry-token Hive registry token for usage metrics reporting (env: HIVE_REGISTRY_TOKEN) + --hive-persisted-documents-endpoint [EXPERIMENTAL] Hive CDN endpoint for fetching the persisted documents. requires the "--hive-persisted-documents-token " option + --hive-persisted-documents-token [EXPERIMENTAL] Hive persisted documents CDN endpoint token. requires the "--hive-persisted-documents-endpoint " option + --hive-cdn-endpoint Hive CDN endpoint for fetching the schema (env: HIVE_CDN_ENDPOINT) + --hive-cdn-key Hive CDN API key for fetching the schema. implies that the "schemaPathOrUrl" argument is a url (env: HIVE_CDN_KEY) + --apollo-graph-ref Apollo graph ref of the managed federation graph (@) (env: APOLLO_GRAPH_REF) + --apollo-key Apollo API key to use to authenticate with the managed federation up link (env: APOLLO_KEY) + --disable-websockets Disable WebSockets support + --jit Enable Just-In-Time compilation of GraphQL documents (env: JIT) + -V, --version output the version number + --help display help for command Commands: - supergraph [options] [schemaPathOrUrl] serve a Federation supergraph provided by a compliant composition tool such as GraphQL Mesh or Apollo Rover - subgraph [schemaPathOrUrl] serve a Federation subgraph that can be used with any Federation compatible router like Hive Gateway or Apollo Router - proxy [options] [endpoint] serve a proxy to a GraphQL API and add additional features such as monitoring/tracing, caching, rate limiting, security, and more - help [command] display help for command + supergraph [options] [schemaPathOrUrl] serve a Federation supergraph provided by a compliant composition tool such as Mesh Compose or Apollo Rover + subgraph [schemaPathOrUrl] serve a Federation subgraph that can be used with any Federation compatible router like Apollo Router/Gateway + proxy [options] [endpoint] serve a proxy to a GraphQL API and add additional features such as monitoring/tracing, caching, rate limiting, security, and more + help [command] display help for command + ``` -All arguments can also be configured in the config file. +All arguments can also be configured in the config file. ### Environment Variables diff --git a/packages/web/docs/src/pages/docs/api-reference/gateway-config.mdx b/packages/web/docs/src/pages/docs/api-reference/gateway-config.mdx index 3b93cb568..af0bb83a9 100644 --- a/packages/web/docs/src/pages/docs/api-reference/gateway-config.mdx +++ b/packages/web/docs/src/pages/docs/api-reference/gateway-config.mdx @@ -20,7 +20,7 @@ The following list of files are loaded by default, sorted by priority: - `gateway.config.mjs` - `gateway.config.cjs` -Both TypeScript (`*.ts`) and JavaScript (`*.js`) config filetypes are supported. +Both TypeScript (`*.ts`) and JavaScript (`*.js`) config filetypes are supported. ## Supergraph Related @@ -46,7 +46,7 @@ export const gatewayConfig = defineConfig({ }) ``` - + For Hive Registry and Apollo GraphOS, you probably don't need to provide the `supergraph` option. @@ -290,7 +290,6 @@ export const gatewayConfig = defineConfig({ }) ``` - By default, Hive Gateway introspects the schema from the endpoint. And if it fails, it skips the validation and schema aware features. But if Hive CDN endpoint and key have been provided in the configuration, Hive Gateway will fetch the schema from the Hive CDN. @@ -312,8 +311,6 @@ export const gatewayConfig = defineConfig({ }) ``` - - #### `endpoint` The URL of the GraphQL endpoint to proxy requests to. @@ -575,3 +572,11 @@ order to have a better scalability, you can provide a custom PubSub. ### `healthCheckEndpoint` and `readinessCheckEndpoint` [Learn more about Health Check and Readiness Check](/docs/gateway/monitoring-tracing#healthcheck) + +### `disableWebsockets` + +By default Hive Gateway starts a WebSocket server, using the same path than the normal HTTP +endpoint. This allows your gateway to be compatible with Subscriptions over WebSockets for incoming +client subscriptions out of the box. + +This option allows you to disable this WebSockets server. diff --git a/packages/web/docs/src/pages/docs/gateway/subscriptions.mdx b/packages/web/docs/src/pages/docs/gateway/subscriptions.mdx index 06041db9e..eeee53bb3 100644 --- a/packages/web/docs/src/pages/docs/gateway/subscriptions.mdx +++ b/packages/web/docs/src/pages/docs/gateway/subscriptions.mdx @@ -3,7 +3,7 @@ searchable: false title: 'Gateway: GraphQL Subscriptions' --- -import { Callout } from '@theguild/components' +import { Callout, Tabs } from '@theguild/components' # Subscriptions @@ -17,142 +17,85 @@ protocols: - [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: +Clients connecting to the Hive Gateway may use either: - [GraphQL over SSE](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverSSE.md) +- [GraphQL over WebSocket with `graphql-ws`](https://github.com/enisdenjo/graphql-ws) -## Example +## When to use subscriptions -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. +GraphQL subscriptions allows to keep the client updated in real time. -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. +Most of the time, a PubSub system is used to propagate events in the backend system. A client can +use subscriptions to receive those events, augmented with all the data it needs using the GraphQL +ability to resolve additional fields. -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 +```mermaid +sequenceDiagram; + Client->>Server: Subscription Query + PubSub->>Server: Event (data changed, new entry added, etc...) + Note over Server: Resolvers runs to construct a new result based on event + Server->>Client: Result + PubSub->>Server: Event (data changed, new entry added, etc...) + Note over Server: Resolvers runs to construct a new result based on event + Server->>Client: Result ``` -### Products service +Subscriptions can be used for applications that relies on events or live data, such as chats, IoT +sensors, alerting, stock prices, etc... -```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' +[Learn more about Subscriptions](https://graphql.org/learn/subscriptions/) -const typeDefs = parse(/* GraphQL */ ` - type Product @key(fields: "id") { - id: ID! - name: String! - price: Int! - } +## Subscriptions in Gateways - type Subscription { - productPriceChanged: Product! - } -`) +In the context of a gateway, subscriptions are forwarded from the client to the subgraph +implementing the subscribed field. -const yoga = createYoga({ schema: buildSubgraphSchema([{ typeDefs, resolvers }]) }) +With the power of the Gateway, each events received from the upstream subgraph will be augmented +with the requested data from other subgraphs, and then sent to the client. -const server = createServer(yoga) +The Hive Gateway also abstract away the underlying protocol used to transport the data. A client can +use a different transport than the one used to connect with the upstream subgraph. -server.listen(40001, () => { - console.log('Products subgraph ready at http://localhost:40001') -}) +```mermaid +flowchart LR + C1["Client"] + C2["Client"] + G["Gateway"] + S1["Products Subgraph"] + S2["Reviews Subgraph"] + C1 -->|Subscribes with SSE| G + C2 -->|Subscribes with WebSocket| G + G -->|Subscribes with either \nSSE, WebSocket or \nHTTP Callbacks| S1 + G -->|Resolves additional fields| S2 ``` -### Reviews service +## Configure subgraph transport -```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' +By default, Hive Gateway will always try to use the same transport for queries, mutations and +subscriptions. -const typeDefs = parse(/* GraphQL */ ` - extend type Product @key(fields: "id") { - id: ID! @external - reviews: [Review!]! - } +In the case of HTTP, the default is to protocol is +[GraphQL over SSE](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverSSE.md). +We highly recommend it, since it's the most performant and idiomatic. - type Review { - score: Int! - } -`) +If your subgraph doesn't implement subscriptions over SSE, you can configure Hive Gateway to use +[GraphQL over WebSocket](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverWebSocket.md) +or +[HTTP Callback](https://www.apollographql.com/docs/router/executing-operations/subscription-callback-protocol/). -const yoga = createYoga({ schema: buildSubgraphSchema([{ typeDefs, resolvers }]) }) +Whichever protocol is used by Hive Gateway to subscribe to the upstream subgraphs, downstream +clients can subscribe to the gateway using any supported protocol. -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 +### 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: +Please note that WebSocket for communications between Hive Gateway and subgraphs are suboptimal +compared to other possible transports. We recommend using either SSE or HTTP Callbacks instead. ```ts filename="gateway.config.ts" import { defineConfig, type WSTransportOptions } from '@graphql-hive/gateway' @@ -165,59 +108,8 @@ export const gatewayConfig = defineConfig({ options: { subscriptions: { kind: 'ws', + // override the path if it is different than normal http 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. - - - 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](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverWebSocket.md#connectioninit). - - - 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. -
- -```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 } } } @@ -225,50 +117,7 @@ export const gatewayConfig = defineConfig({ }) ``` -The contents of the payload will be available in `graphql-ws` connectionParams: - -```json -{ - "connectionParams": { - "token": "" - } -} -``` - - - This is also what Apollo Router when [propagating auth on - WebSockets](https://www.apollographql.com/docs/router/executing-operations/subscription-support/#websocket-auth-support). - - -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}']] - } - } - } - } -}) -``` - - - 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 +### Subscriptions using HTTP Callback If your subgraph uses [HTTP Callback protocol for subscriptions](https://www.apollographql.com/docs/router/executing-operations/subscription-callback-protocol/), @@ -302,10 +151,11 @@ export const gatewayConfig = defineConfig({ }) ``` -## Subscriptions transport configuration +### Subscriptions using mixed protocols -By default, subscriptions will use the same transport than queries and mutation. This can be change -using the `transportEntries` option. +Hive Gateway supports using different transport for different subgraphs. 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: @@ -317,19 +167,15 @@ The key of each entry determine which subgraph will be impacted: 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: +For 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' +import { defineConfig, type WSTransportOptions } from '@graphql-hive/gateway' export const gatewayConfig = defineConfig({ transportEntries: { @@ -342,7 +188,7 @@ export const gatewayConfig = defineConfig({ connectionParams: { token: '{context.headers.authorization}' } - } + } satisfies WSTransportOptions } } }, @@ -360,6 +206,150 @@ export const gatewayConfig = defineConfig({ }) ``` +### Propagation of authentication and headers + +Hive Gateway can propagate the downstream client's `Authorization` header (or any other header) to +the upstream subgraph. + +The propagation of headers is different if you use pure HTTP transports (SSE or HTTP Callbacks) or +WebSockets. + + + + + +Propagation of headers for pure HTTP subscription transports follow the +[same configuration than normal upstream requests](http://localhost:3000/docs/gateway/other-features/header-propagation). + +```ts +import { defineConfig } from '@graphql-hive/gateway' + +export const gatewayConfig = defineConfig({ + propagateHeaders: { + fromClientToSubgraphs({ request }) { + return { + authorization: request.headers.get('authorization') + } + } + } +}) +``` + + + + + +The recommended way is to use the +[`ConnectionInit` message payload](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverWebSocket.md#connectioninit). +It is also possible, but not recommended, to propagate HTTP headers by sending them alongside the +WebSocket upgrade request. + + + 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. +
+ + + + + +```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}' + // You can also add any other field here. + // The only limitation is that value must be serializable with JSON.stringify. + } + } satisfies WSTransportOptions + } + } + } + } +}) +``` + +On subgraph side, the contents of the payload will be available in the `graphql-ws` context field +`connectionParams`: + +```json +{ + "connectionParams": { + "token": "" + } +} +``` + +This is also what Apollo Router do when +[propagating auth on WebSockets](https://www.apollographql.com/docs/router/executing-operations/subscription-support/#websocket-auth-support). + + + + + +WebSocket transport does not take in account the `propagateHeaders` option, because they should be +forwarded using `connectionParams`. + +It is still possible to forward headers if your subgraph is not compatible with `connectionParams`. + +The headers will be sent only with the upgrade request. They will not be sent again during the +lifecycle of the subscription. + +```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}'] + // You can also add any other header here. + ] + } + } + } + } +}) +``` + + + + + +
+ +
+ +## Configure Client subscriptions + +Client subscriptions are enabled by default and compatible with both +[GraphQL over SSE](https://github.com/graphql/graphql-over-http/blob/main/rfcs/GraphQLOverSSE.md) +and [GraphQL over WebSocket with `graphql-ws`](https://github.com/enisdenjo/graphql-ws). + +The default endpoint for subscriptions is `/graphql` and follow the `graphqlEndpoint` option, as for +queries and mutations. + +You can disable WebSockets server by using `disableWebsockets` option in the config file or by +providing `--disable-websockets` option to the `hive-gateway` CLI. + ## Closing active subscriptions on schema change When the schema changes in Hive Gateway, all active subscriptions will be completed after emitting @@ -378,7 +368,423 @@ the following execution error: } ``` - - 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). - +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). + +## 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 + +```sh npm2yarn +npm i graphql-yoga @apollo/subgraph graphql +``` + +### Services + +In this example, we will compose 2 services: + +- `Products` which contains the products data +- `Reviews` which contains reviews of products + +Those 2 services needs to be run in parallel of the gateway. + + + + + +```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') +}) +``` + + + + + +```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') +}) +``` + + + + +### Supergraph + +Once all services have been started, we can generate a supergraph schema. It will then be served by +the Hive Gateway. + +You can generate this schema with either [GraphQL Mesh](https://graphql-mesh.com/) or +[Apollo Rover](https://www.apollographql.com/docs/rover/). + + + + +To generate a supergraph with Apollo Rover, you first need to create a configuration file describing the +list of subgraphs: + +```yaml filename="supergraph.yaml" +federation_version: =2.3.2 +subgraphs: + products: + routing_url: http://localhost:40001 + schema: + subgraph_url: http://localhost:40001 + inventory: + routing_url: http://localhost:40002 + schema: + subgraph_url: http://localhost:40002 +``` + +You can then run the Rover command to generate the supegraph schema SDL: + +```sh +rover supergraph compose --config ./supergraph.yaml > supergraph.graphql +``` + +For more details about how to use Apollo Rover, please refer to the +[official documentation](https://www.apollographql.com/docs/rover/). + + + + +To generate a supergraph schema with GraphQL Mesh, you first need a configuration file describing the list of subgraphs: + +```ts filename="mesh.config.ts" +import { defineConfig, loadGraphQLHTTPSubgraph } from '@graphql-mesh/compose-cli' + +export const composeConfig = defineConfig({ + subgraphs: [ + { + sourceHandler: loadGraphQLHTTPSubgraph('Products', { + endpoint: 'http://localhost:40001/graphql' + }) + }, + { + sourceHandler: loadGraphQLHTTPSubgraph('Reviews', { + endpoint: 'http://localhost:40002/graphql' + }) + } + ] +}) +``` + +You can then run the Mesh command to generate the supergraph schema DSL: + +```sh +npx mesh-compose > supegraph.graphql +``` + +For more details about how to use GraphQL Mesh, please refer to the +[official documentation](https://the-guild.dev/graphql/mesh/v1/getting-started#generating-the-supergraph). + + + + + +### Start Gateway + +You can now start the Hive Gateway. Without any configuration provided, the Gateway will load the +supergraph file `supergraph.yaml` from the current directory, and serve it with a set of sensible +default features enabled. + +```sh +hive-gateway supergraph +``` + +### Subscribe + +By default, subscriptions are enabled and handles both WebSockets and SSE transport. + +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)). + +Most clients offers a way to use subscriptions over SSE. You can find here examples for Apollo +Client and Relay, please refer to the +[Recipes for Clients Usage section of `graphql-sse` documentation](https://the-guild.dev/graphql/sse/recipes#client-usage) +for other clients setups. + + + + + +To quickly test subscriptions, you can use `curl` in your terminal to subscribe to the gateway. +`curl` has native support of SSE. + +```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"}' +``` + + + + + +To enable SSE support using Apollo Client, you can create a simple +[Apollo Link](https://www.apollographql.com/docs/react/api/link/introduction) using +[`graphql-sse`](https://the-guild.dev/graphql/sse) library. + +```ts filename="client.ts" +import { GraphQLError, print } from 'graphql' +import { Client, ClientOptions, createClient } from 'graphql-sse' +import { ApolloClient } from '@apollo/client' +import { ApolloLink, FetchResult, Observable, Operation } from '@apollo/client/core' + +class SSELink extends ApolloLink { + private client: Client + + constructor(options: ClientOptions) { + super() + this.client = createClient(options) + } + + public request(operation: Operation): Observable { + return new Observable(sink => { + return this.client.subscribe( + { ...operation, query: print(operation.query) }, + { + next: sink.next.bind(sink), + complete: sink.complete.bind(sink), + error: sink.error.bind(sink) + } + ) + }) + } +} + +export const client = new ApolloClient({ + cache: new InMemoryCache(), + link: new SSELink({ url: 'http://localhost:4000/graphql' }) +}) +``` + +[Learn more on `graphql-sse` documentation.](https://the-guild.dev/graphql/sse/recipes#with-apollo) + + + + + +To enable support of SSE using Relay, you can create a custom +[Network Layer](https://relay.dev/docs/guides/network-layer/) with +[`graphql-sse`](https://the-guild.dev/graphql/sse) library. + +```ts filename="network.ts" +import { createClient } from 'graphql-sse' +import { Network, Observable, RequestParameters, Variables } from 'relay-runtime' + +const subscriptionsClient = createClient({ + url: 'http://localhost:4000/graphql' +}) + +// yes, both fetch AND subscribe can be handled in one implementation +function fetchOrSubscribe(operation: RequestParameters, variables: Variables) { + return Observable.create(sink => { + if (!operation.text) { + return sink.error(new Error('Operation text cannot be empty')) + } + return subscriptionsClient.subscribe( + { operationName: operation.name, query: operation.text, variables }, + sink + ) + }) +} + +export const network = Network.create(fetchOrSubscribe, fetchOrSubscribe) +``` + +[Learn more on `graphql-sse` documentation.](https://the-guild.dev/graphql/sse/recipes#with-relay) + + + + + + + + + +You can subscribe to the gateway through WebSockets. + +Most clients offers a way to use subscriptions over WebSockets. You can find here examples for +Apollo Client and Relay, please refer to the [Recipes section of +`graphql-ws` documentation]https://the-guild.dev/graphql/ws/recipes) for other clients setups. + + + + + +To quickly test subscriptions, you can use [`wscat`](https://github.com/websockets/wscat) in your +terminal to subscribe to the gateway. + +First, begin the connection with: + +```sh +npx wscat \ + -c http://localhost:4000/graphql \ + -s graphql-ws \ + --wait 600 -x '{"type": "connection_init"}' # Send this message directly, to avoid initialization timeout +``` + +Once connected, you can start a subscription: + +``` +> {"type": "subscribe", "id": "operation-1", "payload": {"query":"subscription OnProductPriceChanged { productPriceChanged { name price reviews { score } } }","operationName":"OnProductPriceChanged"}} +``` + + + + + +To enable subscriptions over WebSockets with Apollo Client, you have to use the +[`GraphQLWsLink`](https://www.apollographql.com/docs/react/api/link/apollo-link-subscriptions), +which relies on [`graphql-ws`](https://github.com/enisdenjo/graphql-ws) library. + +You will need to use a +[`split`](https://www.apollographql.com/docs/react/api/link/introduction#directional-composition) +link to either use classic HTTP for queries and mutation, or WebSockets for subscriptions if you +don't want to use WebSockets for all operations (which is the most common approach). + +```ts filename="client.ts" +import { createClient } from 'graphql-ws' +import { HttpLink, split } from '@apollo/client' +import { GraphQLWsLink } from '@apollo/client/link/subscriptions' +import { getMainDefinition } from '@apollo/client/utilities' + +export const client = new ApolloClient({ + cache: new InMemoryCache(), + // Use the `split` link to send queries and subscription with a different link. + link: split( + ({ query }) => { + const definition = getMainDefinition(query) + return definition.kind === 'OperationDefinition' && definition.operation === 'subscription' + }, + new GraphQLWsLink(createClient({ url: 'ws://localhost:4000/graphql' })), + new HttpLink({ uri: 'http://localhost:4000/graphql' }) + ) +}) +``` + +[Learn more about this on official documentation.](https://www.apollographql.com/docs/react/api/link/apollo-link-subscriptions) + + + + + +Relay doesn't support WebSockets out of the box, but you can easily enable this feature by setting +up a [Network Layer](https://relay.dev/docs/guides/network-layer/) using +[`graphql-ws`](https://github.com/enisdenjo/graphql-ws) library. + +```ts filename="network.ts" +import { createClient } from 'graphql-ws' +import { Network, Observable } from 'relay-runtime' + +const wsClient = createClient({ + url: 'ws://localhost:3000' +}) + +const subscribe = (operation, variables) => { + return Observable.create(sink => { + return wsClient.subscribe( + { + operationName: operation.name, + query: operation.text, + variables + }, + sink + ) + }) +} + +export const network = Network.create(fetchQuery, subscribe) +``` + +[Learn more on official Relay documentation.](https://relay.dev/docs/guided-tour/updating-data/graphql-subscriptions/#configuring-the-network-layer) + + + + + + + +