# graphql-router

A customized Apollo Router implementation for PDE+GO federated GraphQL architecture, aggregating multiple subgraphs into a unified API.

## Prerequisites

### Install protobuf
```shell
brew install protobuf
```

### Install Rust
```shell
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```

The project uses Rust 1.95.0 (automatically selected via `rust-toolchain.toml`):
```shell
rustup install 1.95.0
```

### Install Rover CLI
```shell
curl -sSL https://rover.apollo.dev/nix/latest | sh
```

## Quick Start

### Using Make (Recommended)

The project includes a Makefile for convenient development:

```shell
# Generate supergraph schema from subgraphs
make supergraph

# Run router in dev mode with QA config and hot-reload
make dev

# Other useful commands
make build      # Build the project
make test       # Run tests
make check      # Check compilation without building
make fmt        # Format code
make lint       # Run linter
make help       # Show all available targets
```

### Manual Setup

#### 1. Prepare the Supergraph (SDL) File

A supergraph file is a generated schema that combines multiple subgraph schemas into one unified graph. It contains all the types, directives, and relationships from the individual subgraphs and is used to enable query execution across the entire federated architecture. This combined schema is what the router uses to route and resolve queries.

When testing local subgraph changes, you can run the router with a composed supergraph SDL file. After making changes to a local subgraph, remember to regenerate the supergraph file. With hot reload enabled, the router will automatically detect and apply these changes without needing a manual restart.

Here are multiple ways to generate the supergraph file:

**Option A: Automated composition (recommended for local development)**
```shell
./scripts/generate-supergraph.sh
```
This script will:
- Introspect each subgraph
- Write individual schemas to `subgraphs/` directory
- Generate `supergraph.yaml` config
- Compose the final `supergraph.graphql` file

**Option B: Manual composition**

Edit `scripts/supergraph.yaml` with your local subgraph URLs. Ensure each relevant subgraph is running locally on a distinct port, then:
```shell
rover supergraph compose --config ./scripts/supergraph.yaml > ./supergraph.graphql
```

**Option C: Fetch from Apollo Studio (requires APOLLO_KEY)**
```shell
rover supergraph fetch graphql-theorchard@qa > supergraph.graphql
```

#### 2. Configure the Router

For local development, copy `config-qa.yaml` to `config.yaml`:
```shell
cp config.yaml.shadow config.yaml
```

Then, in the `override_subgraph_url` section, update the URLs to point to your local subgraphs, ensuring each subgraph is running on a distinct port. Be sure to use the same local subgraph URLs and ports when composing the supergraph file and running the router, so that your schema changes are properly reflected.

Alternatively, you can run the router against local subgraphs using the QA schema. While this won't reflect local schema changes, it allows you to test updates to field resolvers and connectors.

**Available configs:**
- `config.yaml` - Local development (gitignored)
- `config-qa.yaml` - QA environment
- `config-uat.yaml` - UAT environment
- `config-prod.yaml` - Production environment

#### 3. Run the Router

**Build and install:**
```shell
cargo install --locked --path .
```

**Start with hot-reload:**
```shell
cargo run -- --dev --config config.yaml --hot-reload --supergraph supergraph.graphql
```

The `--hot-reload` flag watches for supergraph changes and automatically reloads.

## Custom Plugins

The router includes two custom plugins (located in `src/plugins/`):

### 1. Require Apollo Client Name (`theorchard.require_apollo_client_name`)

**Location:** `src/plugins/require_apollo_client_name.rs`

Enforces the presence of the `Apollographql-Client-Name` header, used for tracking field usage in Apollo Studio and DataDog.

### 2. Auth Enforcement (`pde.auth_enforcement`)

**Location:** `src/plugins/auth_enforcement/`

Validates the `Authorization` header and reports telemetry to DataDog under the `check_auth` span.

**Configuration options:**
- `Ok` - Allow request without flagging
- `Warning` - Allow request but flag in DataDog
- `Block` - Reject with 401 Unauthorized and flag as error

**JWT rule options:**
- `invalid_jwt` - Behavior for malformed tokens, bad signatures, wrong issuer or audience, or missing required claims
- `expired_jwt` - Behavior for otherwise valid tokens whose `exp` claim has passed

**JWT validation settings:**
- `issuers` - Allowed JWT issuers. Ordering matters when `jwks_uri` is omitted: `issuers[0]` is treated as the primary issuer and is used to derive the JWKS URL (`{issuers[0]}/.well-known/jwks.json`). The same `https`-only scheme restriction applies to the derived URI as to an explicit `jwks_uri`.
- `audiences` - Allowed JWT audiences
- `jwks_uri` - Optional JWKS endpoint override; defaults to `{issuers[0]}/.well-known/jwks.json`. Must use `https://`; plain `http://` is only permitted for `localhost` and `127.0.0.1` (local development/testing).
- `jwks_lifespan_secs` - JWKS cache refresh interval in seconds; defaults to `3600`

If `jwks_uri` is explicitly set, issuer ordering does not affect JWKS endpoint selection.

**Tracked metrics:**
- Auth method
- Client information
- JWT issuer and subject
- HMAC sender/recipient

**Note:** To develop or use these custom plugins, you must build from source with Cargo. The pre-compiled binary does not include custom plugins.

## Using Pre-compiled Binary (Faster Alternative)

For local development focused on testing subgraphs (not plugin development), using the pre-compiled Apollo Router binary is significantly faster than building from source.

**Trade-off:** Custom plugins won't be loaded, but this typically doesn't impact local subgraph testing.

### Setup

**1. Download the binary:**
```shell
curl -sSL https://router.apollo.dev/download/nix/latest | sh
```

**2. Prepare supergraph file:**
Follow the same steps as above to generate or fetch `supergraph.graphql`.

**3. Update config (if needed):**

If using an older config format, upgrade it:
```shell
./router config upgrade config-qa.yaml
```

**4. Run the binary:**
```shell
./router --hot-reload -c config.yaml -s supergraph.graphql
```

## Local Docker Build

The `Dockerfile` builds the router from source — required because the custom Rust plugins (`pde.auth_enforcement`, `theorchard.require_apollo_client_name`) are statically linked. A clean build is slow (~20–30 min on a laptop); subsequent builds reuse BuildKit cache mounts for the cargo registry and `target/` directory, so unchanged crates skip recompilation.

### Requirements

- **BuildKit enabled.** The Dockerfile uses `RUN --mount=type=cache` directives. Docker Desktop and recent colima versions enable BuildKit by default. CI agents must also have BuildKit enabled.
- **Apple Silicon (arm64) Macs: Rosetta must be enabled** in the VM. The parent image is amd64-only, so the Rust toolchain runs as an amd64 binary on your Mac. Without Rosetta the build runs under qemu, which is unreliable for Rust — the linker (`collect2`) segfaults on large crates and `jemalloc`'s configure script fails.
- **At least 8 GB allocated to the Docker VM.** `rustc` peaks around 4–6 GB compiling `apollo-federation` and the router crate.

### Enabling Rosetta on colima

Edit `~/.colima/default/colima.yaml`:

```yaml
vmType: vz
rosetta: true
```

`vmType: vz` cannot be changed after the VM is created — if your VM is currently using `qemu`, recreate it:

```shell
colima delete
colima start --vm-type vz --rosetta --cpu 4 --memory 8
```

If `vmType: vz` is already set, just toggle rosetta and restart:

```shell
colima stop
# edit colima.yaml: rosetta: true
colima start
```

Verify with `docker run --rm --platform linux/amd64 alpine uname -m` — should print `x86_64` and run instantly (no qemu warm-up).

### Build and run

```shell
docker compose build router      # ~25 min first time, ~30 s thereafter
source .env
docker compose up router
```

Alternatively you can use the make commands:

```sh
make build_deploy
make up_deploy
```

You will see a warning at startup:

```
The requested image's platform (linux/amd64) does not match the detected host platform (linux/arm64/v8)
```

This is informational — Rosetta is translating the amd64 binary. Not a failure.

### Troubleshooting

- **Router exits code 0 silently or `--version` returns nothing** — you have a stale image with a stub binary. Force a clean build: `docker compose build --no-cache router`.
- **`configure: error: cannot compute suffix of executables` / `cc: internal compiler error`** — Rosetta is not actually enabled. Confirm `rosetta: true` in `colima.yaml` and that `vmType: vz` (qemu can't use Rosetta).
- **`signal: 9, SIGKILL` / `cannot allocate memory`** — VM is under-provisioned. `colima stop && colima start --memory 12 --cpu 4`.
- **`uplink error: AUTHENTICATION_FAILED ... Only graph tokens can be used with uplink`** — `APOLLO_KEY` is a user token, not a graph token. See the [Apollo Studio Integration](#apollo-studio-integration) section below.
- **`uplink error: ACCESS_DENIED ... cannot access Uplink`** — graph key role is too low. Needs Observer or higher (see below).

## Apollo Studio Integration

If you're an organization admin in Apollo Studio, you can generate `APOLLO_KEY`s to fetch composed supergraph schemas directly.

### API key types — user vs. graph

Apollo Studio issues **two** kinds of API keys, and they are not interchangeable:

| Key type | Prefix | Use |
|---|---|---|
| **User key** | `user:...` | `rover` CLI (fetch / compose / publish), Studio API browsing |
| **Graph key** | `service:...` | Used by the router for two things, both with the same key: (1) Uplink — fetching the supergraph schema (and router config, if managed) at startup; (2) Apollo Studio usage/metrics reporting — enabled automatically whenever `APOLLO_KEY` and `APOLLO_GRAPH_REF` are set, alongside any other exporter (this repo also exports OTLP traces to DataDog). |

If you start the router with a **user** key in `APOLLO_KEY`, Uplink rejects it:

```
uplink error: code=AUTHENTICATION_FAILED
message=Only graph tokens can be used with uplink. User tokens are not supported at this time.
```

To run the router against Uplink, you need a graph API key with **Observer role or higher** ([Apollo docs](https://www.apollographql.com/docs/graphos/routing/license)):
- Apollo Studio → `graphql-theorchard` → Settings → API Keys → **Create graph API key**
- Role: `Observer` (minimum) — lower roles (e.g. `Consumer`) get `ACCESS_DENIED ... cannot access Uplink`
- Export as `APOLLO_KEY=service:graphql-theorchard:xxxxx`

If you only have a user key, **don't** rely on Uplink — fetch the supergraph yourself with `rover` and pass it explicitly (see below). Note that `rover` calls the Studio API (not Uplink), which is the inverse — graph keys typically get `NOT_ALLOWED_BY_USER_ROLE` there, user keys work fine.

### Fetch supergraph from Studio:
Works with a user key (`rover` accepts it):
```shell
rover supergraph fetch graphql-theorchard@qa > supergraph.graphql
```

### Run without SDL file:

With a **graph** `APOLLO_KEY` and `APOLLO_GRAPH_REF` set, the router can run without a local supergraph file (it pulls the current state from Uplink):
```shell
./router --hot-reload -c config.yaml
```

You can still use `override_subgraph_url` in your config to point to local subgraphs.

## Architecture

### Subgraphs
The router federates 17 subgraphs:
- abacus, account, analytics, audience, content-review
- collaborator, distribution, knowledge, knowledge-search
- neighbouring-rights, participant, podcast, product
- publishing, sr-delivery, tax-payment, user

### Configuration
- **Headers:** All request headers propagated to subgraphs (except `x-datadog-*`)
- **Errors:** All subgraph errors included in responses (`include_subgraph_errors: all`)
- **Telemetry:** Traces exported to DataDog via OTLP/HTTP

### Deployment
CI/CD via Jenkinsfile:
- **Environments:** QA (auto-deploy), UAT (optional), Prod
- **Platform:** ECS Fargate on AWS
- **Notifications:** Slack `#graphql` channel
- **Security:** Trivy vulnerability scanning

## Additional Resources

- [Apollo Router Configuration Docs](https://www.apollographql.com/docs/router/configuration/overview/)
- [Apollo Studio Dashboard](https://studio.apollographql.com/graph/graphql-theorchard/variant/qa)
- Project-specific guidance: See `CLAUDE.md`
