# GraphQL Router Middleware in Go

Add custom logic to the router in Go (auth, caching, rate limiting) without running a separate proxy or forking the codebase.

Same binary. No extra hop. Every Go tool works on it.

## The problem

### Why teams bolt on proxies, scripting, or forks

Federated GraphQL needs gateway logic that talks to the rest of your platform. That rarely fits in a single hop, a sandboxed script, or a one-off fork.

### External proxies add a network hop

A sidecar or Envoy filter in front of the router handles the custom logic, at the cost of an extra hop, another process to run, and metrics that live in two places.

### Scripting languages limit what you can reach for

Lua, JavaScript, or sandboxed WASM filters cover basic shape-shifting, but they can't call your company's Go client library, your internal gRPC service, or the cache SDK your platform team already ships.

### Forked gateways drift

Copying the router source to add a few hooks means inheriting every merge conflict forever. Upstream fixes stop being free.

## Our solution

### Add custom gateway logic without running a second process

Custom modules are Go packages linked into the router binary. Your code runs in the same process as routing and federation, so you can reuse internal clients and observability without adding another network hop or maintaining a fork.

### Each interface hooks into a specific point

1. One module can implement several interfaces at once, and multiple modules can coexist; set priority when their relative order matters.
2. `RouterOnRequestHandler` runs as early as possible, before built-in auth and parsing.
3. `RouterMiddlewareHandler` runs after the operation is parsed, with access to the GraphQL operation and query plan stats.
4. `EnginePreOriginHandler` and `EnginePostOriginHandler` wrap each subgraph fetch and response.
5. `Provisioner` and `Cleaner` run at router start and shutdown for module lifecycle.
6. At request time, handlers fire in-process in the same goroutine as the request, with access to the full request context.

## Custom modules

### Before & After

| Before Cosmo | With Cosmo |
| --- | --- |
| External proxy layer for custom logic | In-process Go module, zero network hop |
| Scripting language with limited reach | Full Go ecosystem and compiled performance |
| A single "on request" hook, workarounds for the rest | Six distinct hooks for precise lifecycle control |
| Hard to test: proxy plus router plus test harness | Plain Go: go test, pprof, delve all apply |

### The six interfaces

| Interface | When it runs |
| --- | --- |
| `RouterOnRequestHandler` | Earliest intercept, before auth and parsing |
| `RouterMiddlewareHandler` | After the operation is known, with query plan stats |
| `EnginePreOriginHandler` | Before each subgraph fetch |
| `EnginePostOriginHandler` | After each subgraph response |
| `Provisioner` | Module setup on router start |
| `Cleaner` | Module teardown on router shutdown |

## What a module can see

- GraphQL operation name, type, hash, content
- Query plan stats (subgraph fetch count, etc.)
- Request context and per-request store
- Subgraph name, ID, and URL (for pre/post origin hooks)
- Authentication info (readable and modifiable)

## How custom Go modules work

### Implement interfaces

Pick the hook point(s) needed. A module can implement more than one interface.

### Config lives in router YAML, not scattered env vars.

### Register the module

Register with the router. Set priority if order relative to other modules matters. Map YAML config values to struct fields with tags.

### Build the router binary

Compile the router with your modules included. Output is a single binary.

### Handlers fire in-process

At runtime, each hook fires at its point in the request lifecycle in the same goroutine as the request, with access to the full request context.

## Use cases

### Patterns teams ship first

Auth, cache, cost controls, and headers at the gateway, without subgraph changes.

### Custom authentication

`RouterOnRequestHandler` runs before the router's built-in auth. Extract credentials, validate against the internal system, either let the request continue or return an early error.

### Gateway-level caching

`EnginePreOriginHandler` checks cache using the operation hash and query plan stats; on a hit it returns early and skips the subgraph fetch. `EnginePostOriginHandler` populates the cache after a miss.

### Per-tenant rate limiting

`RouterMiddlewareHandler` reads operation details and `QueryPlanStats`, estimates cost based on subgraph fetch count, and enforces per-tenant budgets.

### Destination-aware header transformation

`EnginePreOriginHandler` reads `ctx.ActiveSubgraph()` and adds or rewrites headers on the outgoing request. No subgraph code changes.

## Extend the router in Go today

Six hooks, one binary, zero extra hop.
