Router Customizations
Extend your router with custom functionality
You can create customizations for the GraphOS Router or Apollo Router Core to add functionality that isn't available via built-in configuration options. For example, you can make an external call to fetch authentication data for each incoming request.
Customization types
The GraphOS Router supports the following customization types:
-
The Rhai scripting language lets you add functionality directly to your stock router binary by hooking into different phases of the router's request lifecycle.
External co-processing (Enterprise feature)
If your organization has a GraphOS Enterprise plan, you can write custom request-handling code in any language. This code can run in the same container as your router or separately.
The router calls your custom code via HTTP, passing it the details of each incoming client request.
The Apollo Router Core supports customization only through Rhai scripts.
Because Rhai scripts are easier to deploy, we recommend using them if they support your use case. Use external co-processing if your customization needs to do any of the following (which Rhai scripts don't support):
Read or write to disk
Make network requests
Use libraries from a particular language or framework
The request lifecycle
Customizations intervene at specific points of the request lifecycle, depending on the task you want to perform. Each point is represented by a specific service with its own request and response objects.
Each service can have a set of plugins. For requests, the router executes plugins before the service.
For responses, the router executes the plugins after the service.
Each request and response object contains a Context
object, which is carried throughout the entire process. Each request's Context
object is unique. You can use it to store plugin-specific information between the request and response or to communicate between different hook points. (A plugin can be called at multiple steps of the request lifecycle.)
The following flowcharts diagram the entire request lifecycle. The first details the path of a request from a client, through the parts of the router, all the way to your subgraphs. The second details the path of a response from your subgraphs back to the client.
Request path
The router receives a client request at an HTTP server.
The HTTP server transforms the HTTP request into a
RouterRequest
containing HTTP headers and the request body as a stream of byte arrays.The router service receives the
RouterRequest
. It handles Automatic Persisted Queries (APQ), parses the GraphQL request from JSON, and calls the supergraph service with the resultingSupergraphRequest
.The supergraph service calls the query planner with the GraphQL query from the
SupergraphRequest
.The query planner returns a query plan for most efficiently executing the query.
The supergraph service calls the execution service with an
ExecutionRequest
, made up ofSupergraphRequest
and the query plan.For each fetch node of the query plan, the execution service creates a
SubgraphRequest
and then calls the respective subgraph service.Each subgraph has its own subgraph service, and each service can have its own subgraph plugin configuration. The subgraph service transforms the
SubgraphRequest
into an HTTP request to its subgraph. TheSubgraphRequest
contains:the (read-only)
SupergraphRequest
HTTP headers
the subgraph request's operation type (query, mutation, or subscription)
a GraphQL request object as the request body
Once your subgraphs provide a response, the response follows the path outlined below.
Response path
Each subgraph provides an HTTP response to the subgraph services.
Each subgraph service creates a
SubgraphResponse
containing the HTTP headers and a GraphQL response.Once the execution service has received all subgraph responses, it formats the GraphQL responses—removing unneeded data and propagating nulls—before sending it back to the supergraph plugin as the
ExecutionResponse
.The
SupergraphResponse
has the same content as theExecutionResponse
. It contains headers and a stream of GraphQL responses. That stream only contains one element for most queries—it can contain more if the query uses the@defer
directive or subscriptions.The router service receives the
SupergraphResponse
and serializes the GraphQL responses to JSON.The HTTP server sends the JSON in an HTTP response to the client.
Request and response nuances
For simplicity's sake, the preceding diagrams show the request and response sides separately and sequentially. In reality, some requests and responses may happen simultaneously and repeatedly.
For example, SubgraphRequest
s can happen both in parallel and in sequence: one subgraph's response may be necessary for another's SubgraphRequest
. (The query planner decides which requests can happen in parallel vs. which need to happen in sequence.)
Requests run in parallel
Requests run sequentially
Additionally, some requests and responses may happen multiple times for the same operation. With subscriptions, for example, a subgraph sends a new SubgraphResponse
whenever data is updated. Each response object travels through all the services in the response path and interacts with any customizations you've created.
Request and Response buffering
The router expects to execute on a stream of data. In order to work correctly and provide high performance, the following expectations must be met:
Request Path: No buffering before the end of the
router_service
processing stepResponse Path: No buffering
In general, it's best to avoid buffering where possible. If necessary, it is ok to do so on the request path once the
router_service
step is complete.
This guidance applies if you are:
Modifying the router
Creating a native Rust plugin
Creating a custom binary
Customization creation
To learn how to hook in to the various lifecycle stages, including examples customizations, refer to the Rhai scripts and external coprocessing docs.