GraphQL API Gateway Coprocessor Pattern
Running GraphQL API Gateways in production for many years, we have learned that large production environments usually apply the Persisted Operations Pattern. They do so for security and performance reasons.
It's also quite likely one central team runs and maintains the GraphQL API Gateways, while many other teams implement and use the actualy GraphQL APIs.
The Problem
The central team running the GraphQL API Gateway is responsible for the security of the GraphQL API, so they cannot allow any arbitrary GraphQL query to be executed. Additionally, they also cannot allow the individual teams to deploy custom business logic to the GraphQL API Gateway.
However, this creates a problem, because more often than not, you'd want to execute some custom business logic before or after the GraphQL API Gateway executes the GraphQL query. E.g. a team might want to implement custom authentication or authorization logic, or they might want to apply internationalization at the Gateway level so that the response can be cached.
The Solution
The solution to this problem is to allow the individual teams to not just "register" their Operations using the Persisted Operations Pattern, but to also allow them to register a "Coprocessor" for each Operation.
The Coprocessor could be a Webassembly module or a Webhook that is executed at a specific point in the request lifecycle. We also call these Coprocessors "Hooks".
We distinguish between mutating and non-mutating Hooks. Mutating Hooks can modify the request or response, while non-mutating Hooks can only read.
Mutating hooks are useful, e.g. when you'd like to rewrite the GraphQL Operation before it is executed, or when you'd like to modify the response before it is returned to the client.
Non-mutating Hooks are useful, e.g. when you'd like to log the request or response, or when you'd like to apply some custom business logic that does not modify the request or response. E.g. you could send an email after the user has created a new account.
We consider the following Hooks to be useful:
preResolve
: executed before the GraphQL Operation is resolvedmutatingPreResolve
: executed before the GraphQL Operation is resolved, but can modify the requestpostResolve
: executed after the GraphQL Operation is resolvedmutatingPostResolve
: executed after the GraphQL Operation is resolved, but can modify the responsemockResolve
: executed instead of the GraphQL Operation, e.g. for mocking purposes
In addition to Operation-specific Hooks, we also consider the following "global" Hooks to be useful:
onOriginRequest
: executed before the request is sent to the originonOriginResponse
: executed after the response is received from the originonWebsocketConnectionInit
: executed when a new Websocket connection is establishedpostAuthentication
: executed after the user has been authenticatedmutatingPostAuthentication
: executed after the user has been authenticated, but can modify the user object
Hooks enable a wide range of use-cases and are especially useful when using the API Aggregation / Composition Pattern as well as the Persisted Operations Pattern.
We have seen developers using Hooks to sign requests before they are sent to the origin, or to add custom headers to the request. Mutating Hooks can be used e.g. to filter out sensitive data from the response, but there are many more use-cases as hooks are quite flexible.
Example
If you'd like to see an example implementation of this pattern, please have a look at the WunderGraph Server Documentation (opens in a new tab).