Join us for GraphQL Summit, October 10-12 in San Diego. Super Early Bird registration ends soon!
Docs
Try Apollo Studio

External coprocessing in the Apollo Router

Customize your router's behavior in any language


⚠️ This is an Enterprise feature of the Apollo Router. It requires an organization with a GraphOS Enterprise plan.

With external coprocessing, you can hook into the Apollo Router's request-handling lifecycle by writing standalone code in any language and framework. This code (i.e., your coprocessor) can run anywhere on your network that's accessible to the router over HTTP.

You can configure your router to "call out" to your coprocessor at different stages throughout the request-handling lifecycle, enabling you to perform custom logic based on a client request's headers, query string, and other details. This logic can access disk and perform network requests, all while safely isolated from the critical router process.

When your coprocessor responds to these requests, its response body can modify various details of the client's request or response. You can even terminate a client request.

Recommended locations for hosting your coprocessor include:

  • On the same host as your router (minimal request latency)
  • In the same Pod as your router, as a "sidecar" container (minimal request latency)
  • In the same availability zone as your router (low request latency with increased deployment isolation)

How it works

Whenever your router receives a client request, at various stages in the request-handling lifecycle it can send HTTP POST requests to your coprocessor:

1. Sends request
2. Can send request
details to coprocessor
and receive modifications
3
4
5
6. Can send request
details to coprocessor
and receive modifications
7
RouterService
SupergraphService
ExecutionService
SubgraphService(s)
Client
Coprocessor
Subgraphs

This diagram shows request execution proceeding "down" from a client, through the router, to individual subgraphs. Execution then proceeds back "up" to the client in the reverse order.

As shown in the diagram above, only the RouterService and SubgraphService of the request-handling lifecycle can send these POST requests (also called coprocessor requests).

Each supported service can send its coprocessor requests at two different stages:

  • As excecution proceeds "down" from the client to individual subgraphs
    • Here, the coprocessor can inspect and modify details of requests before GraphQL operations are processed.
    • The coprocessor can also instruct the router to terminate a client request immediately.
  • As execution proceeds back "up" from subgraphs to the client
    • Here, the coprocessor can inspect and modify details of the router's response to the client.

At every stage, the router waits for your coprocessor's response before it continues processing the corresponding request. Because of this, you should maximize responsiveness by configuring only whichever coprocessor requests your customization requires.

Multiple requests with SubgraphService

If your coprocessor hooks into your router's SubgraphService, the router sends a separate coprocessor request for each subgraph request in its query plan. In other words, if your router needs to query three separate subgraphs to fully resolve a client operation, it sends three separate coprocessor requests. Each coprocessor request includes the name and URL of the subgraph being queried.

This behavior differs from hooking into RouterService, which can trigger exactly one coprocessor request per stage.

Setup

First, make sure your router is connected to a GraphOS Enterprise organization.

You configure external coprocessing in your router's YAML config file, under the coprocessor key.

Typical configuration

This example configuration sends commonly used request and response details to your coprocessor (see the comments below for explanations of each field):

router.yaml
coprocessor:
url: http://127.0.0.1:8081 # Required. Replace with the URL of your coprocessor's HTTP endpoint.
timeout: 2s # The timeout for all coprocessor requests. Defaults to 1 second (1s)
router: # This coprocessor hooks into the `RouterService`
request: # By including this key, the `RouterService` sends a coprocessor request whenever it first receives a client request.
headers: true # These boolean properties indicate which request data to include in the coprocessor request. All are optional and false by default.
body: false
context: false
sdl: false
response: # By including this key, the `RouterService` sends a coprocessor request whenever it's about to send a client response.
headers: true
body: false
context: false
sdl: false
# You can uncomment this key and populate it like the router: key above to hook into the `SubgraphService`.
# subgraph:

Minimal configuration

You can confirm that your router can reach your coprocessor by setting this minimal configuration before expanding it as needed:

router.yaml
coprocessor:
url: http://127.0.0.1:8081 # Replace with the URL of your coprocessor's HTTP endpoint.
router:
request:
headers: false

In this case, the RouterService only sends a coprocessor request whenever it receives a client request. The coprocessor request body includes no data related to the client request (only "control" data, which is covered below).

Coprocessor request format

The router communicates with your coprocessor via HTTP POST requests (called coprocessor requests). The body of each coprocessor request is a JSON object with properties that describe either the current client request or the current router response.

Body properties vary by the router's current execution stage. See example request bodies for each stage.

Properties of the JSON body are divided into two high-level categories:

  • "Control" properties
    • These provide information about the context of the specific router request or response. They provide a mechanism to influence the router's execution flow.
    • The router always includes these properties in coprocessor requests.
  • Data properties
    • These provide information about the substance of a request or response, such as the GraphQL query string and any HTTP headers. Aside from sdl, your coprocessor can modify all of these properties.
    • You configure which of these fields the router includes in its coprocessor requests. By default, the router includes none of them.

Example requests by stage

RouterRequest

RouterResponse

SubgraphRequest

SubgraphResponse

Property reference

Property / TypeDescription

Control properties

control

string | object

Indicates whether the router should continue processing the current client request. In coprocessor request bodies from the router, this value is always the string value continue.

In your coprocessor's response, you can instead return an object with the following format:

{ "break": 400 }

If you do this, the router terminates the request-handling lifecycle and immediately responds to the client with the provided HTTP code and response body you specify.

For details, see Terminating a client request.

stage

string

Indicates which stage of the router's request-handling lifecycle this coprocessor request corresponds to.

This value is one of the following:

  • RouterRequest: The RouterService has just received a client request.
  • RouterResponse: The RouterService is about to send a client response.
  • SubgraphRequest: The SubgraphService is about to send a request to a subgraph.
  • SubgraphResponse: The SubgraphService has just received a subgraph response.

Do not return a different value for this property. If you do, the router treats the coprocessor request as if it failed.

version

number

Indicates which version of the coprocessor request protocol the router is using.

Currently, this value is always 1.

Do not return a different value for this property. If you do, the router treats the coprocessor request as if it failed.

id

string

A unique ID corresponding to the client request associated with this coprocessor request.

Do not return a different value for this property. If you do, the router treats the coprocessor request as if it failed.

serviceName

string

The name of the subgraph that this coprocessor request pertains to.

This value is present only for coprocessor requests from the router's SubgraphService.

Do not return a different value for this property. If you do, the router treats the coprocessor request as if it failed.

uri

string

The URL of the subgraph that this coprocessor request pertains to.

This value is present only for coprocessor requests from the router's SubgraphService.

Data properties

body

object

The JSON body of the corresponding request or response, depending on this coprocessor request's stage.

  • If a request, this usually contains a query property containing the GraphQL query string.
  • If a response, this usually contains data and/or error properties for the GraphQL operation result.

If your coprocessor returns a different value for body, the router replaces the existing body with that value. This is common when terminating a client request.

headers

object

An object mapping of all HTTP header names and values for the corresponding request or response.

If your coprocessor returns a different value for headers, the router replaces the existing headers with that value.

context

object

An object representing the router's shared context for the corresponding client request.

If your coprocessor returns a different value for context, the router replaces the existing context with that value.

sdl

string

A string representation of the router's current supergraph schema.

This value can be very large, so you should avoid including it in coprocessor requests if possible.

The router ignores modifications to this value.

Responding to coprocessor requests

The router expects your coprocessor to respond with a 200 status code and a JSON body that matches the structure of the request body.

In the response body, your coprocessor can return modified values for certain properties. By doing so, you can modify the remainder of the router's execution for the client request.

The router supports modifying the following properties from your coprocessor:

Do not modify other control properties. Doing so can cause the client request to fail.

If you omit a property from your response body entirely, the router uses its existing value for that property.

Terminating a client request

Every coprocessor request body includes a control property with the string value continue. If your coprocessor's response body also sets control to continue, the router continues processing the client request as usual.

Alternatively, your coprocessor's response body can set control to an object with a break property, like so:

{
"control": { "break": 401 },
"body": {
"errors": [
{
"message": "Not authenticated.",
"extensions": {
"code": "ERR_UNAUTHENTICATED"
}
}
]
}
}

If the router receives an object with this format for control, it immediately terminates the request-handling lifecycle for the client request. It sends an HTTP response to the client with the following details:

  • The HTTP status code is set to the value of the break property (401 in the example above).
  • The response body is the coprocessor's returned value for body.
    • The value of body should adhere to the standard GraphQL JSON response format (see the example above).
    • Alternatively, you can specify a string value for body. If you do, the router returns an error response with that string as the error's message.

The example response above sets the HTTP status code to 400, which indicates a failed request.

You can also use this mechanism to immediately return a successful response:

{
"control": { "break": 200 },
"body": {
"data": {
"currentUser": {
"name": "Ada Lovelace"
}
}
}
}

⚠️ If you return a successful response, make sure the structure of the data property matches the structure expected by the client query!

Failed responses

Your router considers all of the following scenarios to be a failed response from your coprocessor:

  • Your coprocessor doesn't respond within the amount of time specified by the timeout key in your configuration (default one second).
  • Your coprocessor responds with a non-2xx HTTP code.
  • Your coprocessor's response body doesn't match the JSON structure of the corresponding request body.
  • Your coprocessor's response body sets different values for control properties that must not change, such as stage and version.
Edit on GitHub
Previous
Rhai API reference
Next
Native Rust plugins