An Introduction to Cosmo Router: Open-Source Federation v1 and v2 Gateway

cover

Prithwish Nath

min read
Last updated on September 10, 2026 by Brendan Bondurant

TL;DR

The Cosmo Router is the Apache 2.0, Go-based runtime that implements GraphQL Federation for WunderGraph Cosmo. It pulls its federated graph config from a CDN, builds a cached query plan, and splits each client query into subqueries before assembling one response. A customized DataLoader pattern batches nested lookups so a list of N items costs two subgraph requests instead of N+1, and request deduplication collapses identical concurrent queries into a single subgraph request. It holds no session state, so you scale it horizontally, and you extend it with custom modules written in Go, not Rust. You can also put it in front of an existing monolith on day one as a monograph, then split off subgraphs incrementally with no forced cutover. Metrics ship as OpenTelemetry and Prometheus out of the box.

Enterprises have diverse data dependencies — internal microservices with specialized data domains, legacy systems with proprietary data formats, and third-party APIs and SaaS applications with their unique data models and endpoints.

TL;DR: different (and often legacy) technologies that need to be brought together, somehow.

Federated GraphQL has emerged as the go-to solution for such composition in the enterprise sphere, and the Router (or, Gateway) in Federation acts as the linchpin that binds all these disparate data sources together, making them accessible through a single, cohesive API, while still ensuring adaptability. It is, in fact, key to how Federated GraphQL allows for scalable and modular architectures.

Today, we’ll look at WunderGraph Cosmo ’s high-performance, open-source, Federation V1/V2 compatible Router. We’ll cover what it does, why it’s so important to the Cosmo stack, how you can host it yourself, and even customize and extend it with Go code of your own.

What is WunderGraph Cosmo?

WunderGraph Cosmo is a fully open source (Apache 2.0 License) platform to build, manage, and collaborate on federated graphs at scale. It’s a drop-in replacement for Apollo GraphOS/Studio, and is an all-in-one solution that contains a schema registry that can check for breaking changes and composition errors, a blazingly fast Federation V1/V2 compatible Router, and an analytics/distributed tracing platform for federated GraphQL.

GitHub - wundergraph/cosmo: The open-source alternative to Apollo GraphOS

The Cosmo stack is opinionated, and optimized for maximum efficiency and performance. It includes:

  • the Studio (GUI web interface) and the wgc (command-line tool) for managing the platform as a whole — schemas, users, and projects — and accessing analytics/traces.
  • the Router as the component that actually implements GraphQL Federation, routing requests and aggregating responses.
  • the Control Plane, the heart of the platform, providing the core APIs that both the Studio/CLI and the Router consume.

For hosting, you can run the entire platform on-prem on any Kubernetes service (AWS, Azure, Google for production, and Minikube for local dev), or use the managed Cosmo Cloud for all stateful components while you host the stateless router yourself.

State of GraphQL Federation 2026

How are teams governing schema changes, handling production traffic, and measuring Federation success? Share your experience and get early access to the full report. For every valid survey completed, we'll donate $30 to UNICEF .

What is the Cosmo Router?

The Cosmo Router (which you can find in the Cosmo monorepo ) is an open source (Apache 2.0 license) alternative to the Apollo Gateway or Router. It is a HTTP server written in Golang that is the central entry point for your federated GraphQL architectures, and is responsible for routing queries to the correct microservice/subgraph, aggregating their responses, and sending it back to the client in one cohesive format. You can even customize it with pure Go code of your own.

The Router is compatible with Apollo Federation v1 and v2, as well as Open Federation , an open-source specification for federated GraphQL.

How does the Cosmo Router process a request?

Clients make GraphQL requests to the Router’s endpoint, and it intelligently routes these requests to services that can resolve them. Here’s what happens when the Router receives a client request.

  1. Before anything, it needs access to the Federated graph’s schema, which includes the types, fields, and metadata of each subgraph schema, along with their endpoints. This is periodically fetched from a high-availability CDN to make sure the Router has the latest config.
  2. It uses this configuration to generate an optimized Query Plan — cached across requests to minimize work done — to determine how best to decompose the client query into multiple subqueries, which subqueries can be run in parallel and which must proceed sequentially, and how to merge their responses.
  3. The Router then uses the Query Plan to perform the required decomposition into multiple subqueries, and executes them. Each subquery corresponds to a microservice/subgraph that can fulfill a portion of the overall request.
  4. Finally, the Router collates the partial result from each of these subqueries, combines, and assembles them into a cohesive response that matches the structure of the original client query — sending back this assembled response to the client.

The Router makes it so this process is completely opaque to the client. As far as the latter is concerned, the Router was the only endpoint queried for the data, and the federated schema was the only API in play.

How well that cache performs depends on how consistently equivalent queries are shaped before they reach the planner. How Normalization Affects Query Plan Caching in GraphQL Federation covers the forms of normalization that raise the cache hit rate.

Why is the Cosmo Router fast?

The Cosmo Router is powered by graphql-go-tools , a highly mature and optimized GraphQL engine (MIT License) that is the fastest and most reliable implementation for Federation V1 . The Cosmo Router builds on it with its own optimizations.

GitHub - wundergraph/graphql-go-tools: Tools to write high performance GraphQL applications using Go/Golang

Plus, Go itself is designed for performance, having a small memory footprint and fast execution speed, and compiles to machine code directly (as opposed to something like NodeJS, which depends on the JavaScript V8 engine and has some overhead).

Let’s take a look at two ways the Cosmo Router gets big performance gains.

How does it solve the N+1 problem?

When you request data that involves nested relationships across different services (and thus remote joins), say, a User and their Posts, the naïve implementation makes one separate query to resolve the posts field for each User’s Posts.

1
2
3
4
5
6
7
8
9
10
11

Why is this inefficient? Well, how many times are you actually hitting data sources?

  • You fetch all Users in one query (that’s 1 query, to the Users service).
  • For each user, you make a separate query to retrieve their Posts (that’s an additional N queries to the Posts service, where N is the number of Users).

So, if you have 100 users, that’s 1 + 100 = 101 calls made. This is the infamous N+1 problem. With GraphQL, we’ve moved it to the server rather than the client, but it exists regardless, and can slow data fetching to a crawl as the number of Users grows, or if you have further nested data.

The Cosmo Router uses a customized DataLoader pattern (if you’re not familiar with the base implementation, see Lee Byron’s video here. ) to solve this, out of the box. Instead of making separate requests for each User, the Router analyzes the client query ahead of execution to determine which join keys would be required to resolve the remote Posts relationship, and modifies the first subquery (fetching all Users) to also extract those — in this case, our join key would be author_id’s.

1
2
3
4
5
6
7

How efficient is this new method? Let’s see how many queries you’re making now:

  • As usual, you fetch all Users (that’s 1 query).
  • Cosmo Router now batches the Posts retrieval for all 100 users into a single query (that’s 1 more query).

So, in total, you’re now only making 1 + 1 = 2 queries to fetch all the required data, regardless of the number of Users. A significant improvement to the naïve N+1 approach.

Cosmo’s DataLoader is a specialized implementation that does far more than this. In GraphQL servers, the default behavior is to resolve fields depth-first — like going through a list of items one by one, exhaustively resolving each item before moving to the next. Cosmo’s DataLoader 3.0 pattern instead splits the traditional resolver process into two distinct steps:

  1. Breadth-First Data Loading: In the first step, the system walks through the query plan breadth-first. This means it identifies all the data needed from the subgraphs across the fields, loads them, and then merges the results collated from these subgraphs into a single JSON object.
  2. Depth-First Response Generation: After merging the data into a single JSON object, the second step involves walking through this merged JSON object depth-first — constructing a response JSON for the client, according to the structure of the GraphQL Query received from it. This step essentially assembles the final response that will be sent back to the client.

Instead of drilling down into each branch to its deepest level before moving on, Cosmo’s approach gathers data from sibling fields concurrently. This means that as we traverse the query, it collects data from multiple items (siblings) at the same level in the tree structure simultaneously.

Since we are processing sibling fields together, we can automatically batch together the requests for these fields. This batching reduces the number of individual requests made to fetch data, leading to significantly improved efficiency and performance in GraphQL query resolution over the vanilla DataLoader implementation, and is specifically optimized for resolving deeply nested relationships — leading to massive performance gains.

What is request deduplication (Ludicrous Mode)?

When multiple identical requests (asking for the same data) are “in-flight” simultaneously (being processed simultaneously), the Cosmo Router intelligently sends only one request to the origin server, and shares the result with all other active requests.

Cosmo calls this the ‘Ludicrous Mode’ for the Router, and when enabled, it eliminates redundant requests for the same data, optimizing network traffic and reducing the load on the origin server.

While Ludicrous Mode is very situational, and of course doesn’t eliminate the need for processing each client request individually, it is particularly valuable when dealing with frequently executed, read-only requests (e.g. resolving nested lists of entities).

Why is the Cosmo Router stateless?

Because the Router itself doesn’t store any session-specific data between requests, each request it handles is independent. Multiple instances of the Router can thus be provisioned according to your requirements, and building your federated GraphQL architecture with the Cosmo Router means you’ll be able to scale horizontally to handle large numbers of incoming, concurrent requests — a common requirement in enterprise environments.

The predictability gained here is crucial for maintaining (and debugging) a reliable system. Also, being stateless makes your unified API resilient. If one hosted instance of the Router fails, another can seamlessly take over because there’s no session state that needs to be preserved.

The statelessness of the Router is by design, and it ensures scalability and flexibility in the architecture. Each of the actual services (which contains the resolver functions for specific types and fields) is responsible for managing its own state, instead. This allows your services to be developed and scaled independently.

How do you customize the Cosmo Router?

You can extend the functionality of the Cosmo Router by creating custom modules, written in Go.

To create a custom module, you need to implement one or more of these predefined interfaces:

  1. core.RouterMiddlewareHandler — A custom middleware function that will be called for every single client request that goes through the Router.
    For example, you could create middleware that logs incoming GraphQL requests and responses, add or modify headers in the request or response, cache the results of frequently executed queries, validate incoming GraphQL requests and reject invalid queries/mutations before they reach the GraphQL Engine, and more.
  2. core.EnginePreOriginHandler — A custom handler that runs before a request is forwarded to a subgraph, called for every subgraph request.
    You could use it for logging (for debugging/auditing) or header manipulation (for custom auth, security, etc.)
  3. core.EnginePostOriginHandler — A custom handler that runs after a request has been sent to a subgraph but before a response has been passed to the GraphQL Engine, called for every subgraph response.
    You could use this to cache the response (in-memory or Redis), or intercept and handle errors that occur in the subgraph response — logging errors, or formatting error responses consistently for the client.
  4. core.Provisioner — Implements a Module lifecycle hook that is executed when the module is being instantiated. Use it to prepare your module and validate the configuration.
    You can use this hook to configure internal state, or perform any pre-init tasks like resource allocation (establishing a database connection pool), or checking that all required configuration values are present and that they have valid formats and values (and raise exceptions if not).
  5. core.Cleaner — Implements a Module lifecycle hook that is executed after the server is shutdown. The converse of core.Provisioner, it is used to deallocate resources, shut down connections gracefully, and perform any other necessary cleanup.

This approach offers a level of customizability similar to what xcaddy does for the Caddy server, eliminating the complexities associated with writing Rhai scripts to customize a precompiled binary, as is the case with the Apollo Router.

Apollo splits its own customization options across three tiers. Rhai scripts run on the free Apollo Router Core. External coprocessors accept any language, but Apollo's documentation on router customizations restricts them to organizations on a GraphOS plan. Native Rust plugins go further, but they require compiling a custom router binary from source, a step Apollo's documentation on native plugins warns "can introduce unexpected behavior in your router that's difficult to diagnose and support."

Cosmo Router doesn't split that choice into tiers. A Go custom module is the one path to extend it, and it runs in the same Apache 2.0 binary whether you self-host or use Cosmo Cloud, with no paid plan and no Rust toolchain required.

Writing tests for custom modules is also extremely easy. Here is a fully tested example

How does the Cosmo Router handle observability?

The Cosmo Router has been instrumented with OpenTelemetry — an open source observability framework that can collect, process, and export metrics, traces, and logs from applications and services. The Router pushes performance metrics to an OTEL Collector, giving you an end-to-end view of the path taken by your API traffic on a per-request level through your federated graph, along with the specific operation performed in the request.

You can even combine this with OTEL metrics pushed by your subgraphs, to have full control over optimizing your infrastructure. You can read more about instrumenting your individual services/subgraphs with OTEL here .

If you’re using the all-in-one WunderGraph Cosmo platform, you can visualize this data and see exactly how a request progresses through different services in the Analytics tab of your Cosmo dashboard.

If you’re only using the Cosmo Router, any backend compatible with the OpenTelemetry Protocol (OTLP) — Jaeger, DataDog, etc. — can import the metrics generated by the Cosmo Router, meaning you get centralized monitoring and analysis across your entire infrastructure, regardless of your monitoring stack.

With Cosmo’s Router, you also get Prometheus metrics — the battle-tested, open-source (Apache License) service monitoring system — and R.E.D metrics , meaning you have a full metrics stack for fine-grained insights into router traffic, the error/success rates or the average request/response times/sizes of specific operations, and in general, everything you need to identify bottlenecks and optimize the performance of your system.

How do you forward client headers to subgraphs?

When working in federated GraphQL architecture you’ll often need to forward specific client headers to subgraphs. This may be because you need to pass contextual information like caching strategies, auth tokens, user preferences, or just device-specific information so your subgraphs can make decisions based on some client context.

The Cosmo router makes it easy to forward HTTP headers to subgraphs. By default, none are forwarded for security reasons. But you can modify the Router config file (config.yaml in the working directory of the Router) to add a headers root key, and customize it with Cosmo header rules according to your needs. These rules are applied in the order they appear in this YAML file.

config.yaml

1
2
3
4
5
6
7
8

The propagate header rule forwards all matching client request headers to subgraphs.

Once you have that key in place in this YAML file, the named nested key is used for an exact match of the header name (Remember that with the Golang http package, each word separated by a hyphen is capitalized. i.e., x-test-header will become X-Test-Header, and that’s the one you have to use), and the matching nested key is used when you want to use Regex to match a header name.

And of course, if you want to set a default header rule that is applied even if one isn’t set by the client, you can do so.

config.yaml

1
2
3
4
5
6

Can Cosmo Router sit in front of an existing monolith?

Yes. You don't need an existing federated architecture to start.

Cosmo supports a monograph: a graph with federation switched off, limited to a single subgraph. Point wgc monograph create at the routing URL of your router and the URL of your existing GraphQL server, and the router becomes the front door to that server exactly as it is today. Nothing about how it resolves data changes, and you pick up the router's schema registry, traffic shaping, and observability from day one. See the router overview in the Cosmo docs for the rest of what ships in that binary.

When you're ready to carve a bounded context out of the monolith, wgc monograph migrate converts the monograph into a federated graph. From there, you add subgraphs one at a time and compose them in with label matchers: the same incremental pattern federation teams use to migrate off a monolithic schema. How to Migrate from a GraphQL Monolith to Federation covers that phase in more depth, including the strangler-fig pattern and @override.

None of this is gated behind a paid plan. The monograph, the migrate command, and the router itself run on the same Apache 2.0 binary whether you self-host it or run it against Cosmo Cloud, and features like rate limiting and traffic shaping ship in that same binary. Rate Limiting GraphQL Federation with Cosmo Router & Redis covers how to set one up.

How do you test and deploy the Cosmo Router?

The Cosmo Router fetches the latest config of your federated architecture from the Cosmo platform or the CDN. For debugging and local dev, you might want to override that behavior and have it load a config from a local file, instead, like so:

1
2
3
4

Where config.json is your local test config file.

If you wanted to work locally with a copy of your production config instead, you could fetch it like so, before running the command above.

npx wgc router fetch production > config.json

Where production is the name of the federated graph whose router config you want to retrieve.

Similarly, if you wanted to generate a router config locally (without a connection to the Cosmo platform’s Control Plane component) from your subgraphs, pipe that to a file, and use that instead with the docker command above,

npx wgc router compose -i config.json

Finally, the Cosmo Router is a Go application provided as a self-contained Docker container. You can initiate the router by executing a single Docker command.

1
2
3
4
5
6
7
8

💡 You can generate a new GRAPH_API_TOKEN for a federated graph like so:

npx wgc router token create mytoken --graph-name <name>

As the router is stateless, you can deploy multiple instances. WunderGraph Cosmo recommends 3 instances, the box specs of each instance being 2 CPUs with 1 GB of RAM for low-medium traffic projects.

Where to go from here

For most people, the Apollo Router and Gateway will be perfectly fine for federated GraphQL architectures. However, for enterprise use cases, the blocker to adoption is that these two are under the Elastic V2 license , which is a restrictive license the OSI does not consider open source. Plus, enterprise use cases often have unique and strict requirements for data compliance, and vendor lock-in is a real dilemma.

Apollo's routers changed too. Apollo Router Core and GraphOS Router, starting at version 1.60, dropped support for Federation v1 supergraphs when Apollo moved to a native query planner. Cosmo Router still composes both Federation v1 and v2, so a supergraph built years ago keeps working without a rewrite.

That’s why the blazingly fast, fully open-source, Federation V1/V2/Open Federation compatible Cosmo Router (and Cosmo as an alternative platform to Apollo GraphOS/Studio) makes perfect sense in these use cases.

To learn more, go check out their docs here . Also, the WunderGraph Discord can be found here , if you have questions or issues you want to discuss.


Frequently Asked Questions (FAQ)

The Cosmo Router is an open-source (Apache 2.0) HTTP server written in Go that acts as the central entry point for a federated GraphQL architecture. It routes queries to the subgraphs that can resolve them, aggregates their responses, and returns one cohesive result to the client. It is an alternative to the Apollo Gateway or Router, and it is compatible with Apollo Federation v1 and v2 as well as Open Federation.

It first fetches the federated graph configuration from the Cosmo CDN, which gives it the types, fields, and endpoints of every subgraph. It uses that configuration to build a query plan, cached across requests, deciding how to split the client query into subqueries and which of those can run in parallel. It executes the subqueries against the subgraphs, then collates the partial results into a response matching the structure of the original query. The process is opaque to the client, which sees only one endpoint and one schema.

It uses a customized DataLoader pattern. Instead of issuing one request per item in a list, the Router analyzes the query before execution to work out which join keys the nested relationship needs, then modifies the first subquery to extract them. Fetching 100 users and their posts costs 2 requests instead of 101. The post also describes the approach it called DataLoader 3.0: walk the query plan breadth-first to load and merge data from sibling fields together, then walk the merged JSON depth-first to build the client response. That is how the engine worked when this post was published in 2023.

When several identical requests are in flight at the same time, the Router resolves one and shares the result with the others, which removes redundant work and reduces load on the subgraphs. See the request deduplication docs for the current behaviour, which has changed since this post was published.

The Router stores no session-specific data between requests, so every request is independent. That lets you run multiple instances and scale horizontally, and it means another instance can take over if one fails, because there is no session state to preserve. Each subgraph manages its own state instead, so services can be developed and scaled independently.

You write custom modules in Go that implement one or more predefined interfaces, among them core.RouterMiddlewareHandler for client requests, core.EnginePreOriginHandler before each subgraph request, core.EnginePostOriginHandler after each subgraph response, core.Provisioner when the module is instantiated, and core.Cleaner after shutdown. The set of hooks and their behaviour have changed since this post was published, so check the custom modules docs before writing one.

Yes. Cosmo Router is licensed under Apache 2.0 and lives in the open-source Cosmo monorepo. You can self-host it on Docker or Kubernetes, or run it against the managed Cosmo Cloud service, and it is the same binary either way.

Yes. Create a monograph, which points the router at your existing GraphQL server as a single subgraph with federation switched off. That gives you traffic shaping, observability, and a schema registry for that server from day one, with no change to how it resolves data. When ready to split out a piece of the monolith, `wgc monograph migrate` converts it into a federated graph so subgraphs can be added incrementally.

Custom modules are written in Go, the same language the router itself is built in. There is no separate scripting language to learn and no Rust toolchain to install, unlike Apollo Router, where native plugins require compiling a custom router binary from source.

Cosmo Router is Apache 2.0 and written in Go. Apollo Router is source-available under the Elastic License v2 and written in Rust. Cosmo still composes Federation v1 and v2 supergraphs, while Apollo Router 1.60 and later dropped Federation v1 support entirely. Extending Cosmo Router means writing a Go custom module, while extending Apollo Router beyond Rhai scripts means a paid GraphOS plan for coprocessors or compiling a custom Rust binary.