GraphQL Gateway
The GraphQL gateway is the single entry point for all frontend GraphQL traffic. It is a custom Hive Router binary written in Rust: the platform compiles its own router executable with the Evolve plugins built in (“bring your own binary” is the Hive Router plugin model). It composes the domain subgraphs into one federated supergraph and handles cross-cutting concerns like authentication, persisted-document enforcement, and observability — with no Node.js runtime in the request path.
| Component | Source |
|---|---|
| Hive Router | hive-router crate, pinned release tag (see Cargo.toml) |
| Federated token plugin | hive-router-federated-token (own repo, compiled in) |
| Authoring token plugin | in-tree crate (upcoming, ships with the authoring stack) |
The service lives in backend/services/graphql-gateway/: src/main.rs
registers the plugins against the router entrypoint, router.config.yaml is
the production config template, and router.config.local.yaml is used for
local development.
Supergraph composition
Section titled “Supergraph composition”The router loads its supergraph from one of two sources:
- Production: the Hive CDN, polled for updates. Activated by the
HIVE_CDN_ENDPOINT+HIVE_CDN_KEYenvironment variables (the router’s native override turns these intosupergraph.source: hive). - Development: a local file,
supergraph.generated.graphql, composed from the subgraph SDLs with the Hive CLI:
task -d backend/services/graphql-gateway supergraph# or continuously:task -d backend/services/graphql-gateway supergraph:watchThe composition task takes a CMS variable (contentful, the default, or
storyblok) — the two CMS subgraphs are interchangeable and only differ in
which schema.generated.graphql fills the cms slot.
Subgraphs
Section titled “Subgraphs”| Subgraph | Default dev URL | Domain |
|---|---|---|
| account | http://localhost:4001/graphql |
Authentication & customer data |
| catalog | http://localhost:4002/graphql |
Products, categories, search |
| checkout | http://localhost:4003/graphql |
Cart, orders, payments |
| cms | http://localhost:4004/graphql |
CMS content (Contentful or Storyblok) |
| order | http://localhost:4005/graphql |
Order history & management |
| quotes | http://localhost:4006/graphql |
B2B quote requests |
Endpoints
Section titled “Endpoints”| Path | Purpose |
|---|---|
/graphql |
Main GraphQL endpoint (GET/POST; serves GraphiQL in dev) |
/graphql/auth |
Same GraphQL endpoint on the refresh-cookie path |
The router serves any sub-path of the GraphQL endpoint, which is what makes
the cookie-path separation work: the refresh-token cookie is scoped to
/graphql/auth so it is never sent on regular queries, limiting exposure.
Authentication
Section titled “Authentication”Customer authentication is implemented by the federated_token plugin, a
Rust port of the @labdigital/federated-token Apollo plugin. The subgraph
wire format is unchanged — subgraphs still receive base64-JSON
x-access-token / x-refresh-token headers — so the gateway swap is
transparent to every downstream service.
The plugin reads and validates the client’s tokens on each GraphQL request
(cookies first, headers as fallback), injects them on outgoing subgraph
requests, merges tokens minted or rotated by subgraphs back off their
responses, and writes the resulting cookies on the client response.
Configuration (issuer, audience, cookie domain, key rotation) lives in the
plugins.federated_token block of the router config; the symmetric keys
come from FEDERATED_TOKEN_ENCRYPT_KEY_1 / FEDERATED_TOKEN_SIGN_KEY_1.
See Authentication & tokens for token types and the session flow.
The authoring_token plugin (upcoming, part of the authoring stack)
resolves the editor’s CMS OAuth token from the __authoring_session cookie
or the X-Authoring-Cms-Token header and forwards it to subgraphs. Unlike
the customer plugin it never fails a request — enforcement lives in the CMS
subgraph’s authoring resolvers.
Persisted documents
Section titled “Persisted documents”Trusted-document enforcement is native to Hive Router. In production the
persisted_documents config block is enabled with require_id: true: only
operations whose documentId is registered in the Hive CDN (App
Deployments) are accepted, and arbitrary operations — including
introspection — are rejected. There is no bypass header.
In development the block is disabled, because the dev storefront falls back to full queries and there is no local Hive CDN.
Header propagation & store context
Section titled “Header propagation & store context”Header forwarding to subgraphs is declarative router config (the headers:
section of router.config.yaml) — it replaces the custom Apollo data-source
code the Node gateway used:
| Incoming header | Forwarded as | Notes |
|---|---|---|
X-StoreContext-StoreKey |
same | Store context, supplied by the storefront |
X-StoreContext-Locale |
same | |
X-StoreContext-Currency |
same | |
X-Forwarded-For |
X-Client-IP |
Defaults to 127.0.0.1 locally |
See Store context for how services consume these values.
CORS is router config (cors: block). Credentialed requests require
explicit origins (never *): local dev allows http://localhost:3000 and
the Portless storefront alias; production origins are injected through the
CORS_ORIGINS environment variable (a JSON array rendered into the config
at container start).
Observability
Section titled “Observability”The router exports OpenTelemetry traces and metrics over OTLP. The rest of the stack propagates trace context via B3, and the router matches it — extracting incoming B3 context and injecting B3 toward subgraphs so spans stay in one trace. In production the exporter uses OTLP over HTTP (the collector sits behind an L7 proxy that mangles gRPC trailers); locally it exports to the docker-compose OTel Collector.
Configuration
Section titled “Configuration”Hive Router does not interpolate ${VAR} in its YAML. Two mechanisms fill
the gap:
- Container start:
docker-entrypoint.shrendersrouter.config.yamlwithenvsubstfor an allowlisted set of variables (CORS_ORIGINS,JWT_ISSUER,JWT_AUDIENCE,COOKIE_DOMAIN, the token keys, the OTLP endpoint). - Plugin init: the Evolve plugins expand
${VAR}references in their own config blocks at startup, so secrets stay in the environment.
Key environment variables:
| Variable | Purpose |
|---|---|
HIVE_CDN_ENDPOINT / HIVE_CDN_KEY |
Supergraph source (production) |
JWT_ISSUER / JWT_AUDIENCE |
Token claims validated by the plugins |
COOKIE_DOMAIN |
Cookie domain for auth tokens |
FEDERATED_TOKEN_ENCRYPT_KEY_1 / FEDERATED_TOKEN_SIGN_KEY_1 |
Customer token keys (32 bytes) |
CORS_ORIGINS |
Allowed origins (JSON array, production) |
ROUTER_CONFIG_FILE_PATH |
Which config file to load |
Local development
Section titled “Local development”pnpm dev (or pnpm dev:backend) runs the gateway via
task -d backend/services/graphql-gateway dev, which composes the
supergraph and then cargo runs the router on port 4000 against
router.config.local.yaml. A Rust toolchain is required. The Taskfile
provides working defaults for every secret so the router boots without
additional setup.

