Migrating a federated GraphQL router from Apollo to Cosmo
Lessons from coordinating a GraphQL router migration: schema publishing, subgraph deployments, CI/CD changes and the operational work after cutover.
A GraphQL router migration can look deceptively small on an architecture diagram. Replace one box, point traffic at another and carry on. In a federated platform, that box sits between service ownership, schema delivery and the operational habits of the whole team.
At Simply Wall St, my rollout work connected those concerns for the transition from Apollo to WunderGraph Cosmo. The useful lesson was the sequence: establish how schemas reach the new platform, check the services behind them, move traffic and give engineers a way to operate what they have just shipped.
Treat schema publishing as part of the deployment
The router depends on the individual subgraphs agreeing with the schema it serves. A service deployment and its schema publication belong in the same conversation, even when different jobs perform them.
Our rollout included fixing schema publishing for individual subgraphs and dealing with publishing overrides. Those details matter because a healthy router process tells you very little about whether each service can publish the schema the platform expects.
For a migration plan, I would make the dependencies explicit:
| Question | What the rollout needs to establish |
|---|---|
| Which services publish schemas? | An owner and working publication path for each subgraph |
| What checks protect a change? | A clear decision about the checks retained or replaced |
| Which deployment failures block cutover? | A shared view of subgraph readiness |
| What happens after traffic moves? | Monitoring, maintenance and follow-up ownership |
This is also where old automation deserves attention. Leaving an obsolete check in CI can give engineers an extra failure to investigate without improving the new platform.
Move deployment tooling alongside the router
The transition replaced the Apollo Helm release and removed Apollo schema checks and obsolete gateway jobs from CI. Checking subgraph deployments for failures was part of the same rollout, alongside the traffic cutover.
The practical implication is that infrastructure changes and developer workflows need a shared release plan. A router that works in isolation is only one piece; the team also needs to be able to change a service through its normal delivery process.
I find it useful to distinguish three checks: whether the new router can serve requests, whether subgraphs deploy correctly and whether future schema changes can travel through the intended pipeline. They answer different questions. Combining them into a single “migration complete” checkbox hides work.
Use Cloudflare Workers to move traffic gradually
I used Cloudflare Workers to control the cutover at our existing /graphql endpoint. Rather than switching every request in one step, I gradually routed traffic from Apollo to the Cosmo router, increasing the share until Cosmo was handling 100% of requests. Only after that did we fully cut the service over.
This separated the traffic migration from the final service cutover. The public endpoint stayed the same while the Worker controlled which router received requests, giving the rollout a gradual path to full traffic before completing the service transition.
My preference is to make those two milestones explicit in a migration plan: first reach 100% on the new request path, then finish the service cutover. A traffic-routing layer gives the team control over the progression; the actual routing behaviour still needs to be checked against authentication, request bodies and any subscription transports the endpoint supports.
Make the router behaviour reviewable in configuration
The rollout sequence becomes easier to review when the configuration shows what the router is expected to do. The following excerpts are illustrative examples checked against the current public documentation, rather than extracts from the Simply Wall St deployment. They omit graph credentials, execution configuration and environment-specific infrastructure.
Separate readiness from liveness
version: "1"
listen_addr: "0.0.0.0:4500"
log_level: "info"
json_log: true
readiness_check_path: "/readyz"
liveness_check_path: "/livez"
Cosmo’s readiness endpoint returns 200 when the router can accept traffic and 503 otherwise. Liveness checks whether the process is alive. Router configuration reference.
I would connect these to separate Kubernetes probes: readiness controls traffic eligibility; liveness controls restart decisions. Neither proves that every subgraph or important operation works. I would also rehearse a request that crosses multiple subgraphs before accepting a cutover.
Be deliberate about forwarded headers
headers:
all:
request:
- op: propagate
named: Authorization
- op: propagate
named: X-Request-ID
This example forwards only two named headers to all subgraphs. Cosmo also supports rules scoped to individual subgraphs. Header configuration.
The important decision is which services should receive user credentials. I would use per-subgraph rules when only some services need them, and avoid a catch-all rule that forwards every incoming header. Forwarding Authorization does not validate the token or establish permissions; that remains an explicit authentication and authorisation responsibility. A caller-supplied request ID is useful for correlation, but should not become a trusted identity signal.
Give monitoring its own endpoint
telemetry:
metrics:
prometheus:
enabled: true
listen_addr: "0.0.0.0:9091"
path: "/router-metrics"
Cosmo exposes Prometheus metrics on a configurable address and path. Prometheus configuration.
Binding to all interfaces makes this example reachable by a cluster scraper; I would keep that port on a private network and exclude it from public ingress. An endpoint alone is not a monitoring migration. The scraper, dashboards and alerts also need to be updated and checked against the metrics the new router actually emits.
These excerpts belong in one router YAML file, but do not form a complete deployment. Pin the router release and validate against that release’s configuration schema; options in today’s documentation may differ from the version used during an earlier migration. The official Cosmo onboarding guide covers graph creation, schema publication and connecting a router.
The traffic switch is an operational handover
I included a metrics migration guide in the rollout work. The checklist also tracked follow-up items for routing configuration, temporary infrastructure, APM guidance, SLOs and router upgrades.
Those were follow-up responsibilities, rather than evidence that every operational improvement was finished at cutover. Keeping that distinction visible helps a team avoid losing maintenance work once the most visible milestone is over.
A useful handover explains where to look when a request fails, which metrics have changed and who owns the router’s next upgrade. It should be usable by an engineer who did not participate in the migration.
What I would carry into another backend migration
The pattern applies beyond GraphQL: connect the runtime change to the delivery pipeline, then connect both to everyday operations. Prepare the publishing path, establish deployment readiness, coordinate traffic and assign the remaining operational work.
That is the kind of backend and platform engineering I help teams with: making a technical transition understandable enough to release and maintain. If you are planning an API migration, tell me about your platform.