Self-hosted Gateway
The Grafbase Gateway allows you to run a federated graph within your own infrastructure. The gateway operates by either polling the latest graph from the Grafbase API or by passing the federated schema, enabling air-gapped operation.
To install the Grafbase Gateway, run the following command:
curl -fsSL https://grafbase.com/downloads/gateway | bash
In hybrid mode, the gateway fetches the current federated graph from the Grafbase platform. Create a federated graph in the Grafbase API, publish the subgraphs, and the gateway will always have the current graph running.
Start the gateway in hybrid mode with the graph reference and an organization access token:
GRAFBASE_ACCESS_TOKEN=token ./grafbase-gateway \
--config grafbase.toml \
--graph-ref graph@branch
graph-ref
points to a graph created in the Grafbase API and its branch. If the branch is empty, the gateway uses the production branch by default.
Create the organization access token in the account settings under "Access Tokens" and ensure it has permission to read the graph.
The gateway polls for graph changes every ten seconds.
In air-gapped mode, the gateway never calls the Grafbase API. You must provide the federated graph SDL as a file.
Start the gateway in self-hosted mode:
./grafbase-gateway \
--config /path/to/grafbase.toml \
--schema /path/to/federated-schema.graphql \
--listen-address 127.0.0.1:4000
Define the configuration using the TOML file format.
The gateway loads the configuration at startup, and you can hot-reload parts of it during runtime with the --hot-reload
flag. The gateway polls for configuration changes every two seconds when provided a path to the configuration file.
[graph]
path = "/graphql"
introspection = false
path
: The URL path where the GraphQL API is available. Defaults to/graphql
.introspection
: Enables or disables GraphQL introspection. Defaults tofalse
.
[network]
listen_address = "127.0.0.1:5000"
listen_address
: The address to bind to. Defaults to127.0.0.1:5000
. Can be set in the configuration or via the command line interface using the--listen-address
argument.
Set gateway server settings in this section:
[gateway]
timeout = "30s"
subgraph_timeout = "4s"
timeout
: Timeout for slow requests and responses. Default:30s
.subgraph_timeout
: A global timeout for all subgraph requests. A subgraph can override this setting.
Read more about rate limiting.
[gateway.rate_limit.global]
limit = 100
duration = "10s"
You can hot-reload the subgraph rate limit and duration settings.
Read more about rate limit specific for a subgraph.
The Grafbase Gateway requires configuration to enable query batching. Be aware that a large batch of queries can trigger a denial of service attack on the subgraph service or the gateway itself.
[gateway.batching]
enabled = true
limit = 5
enabled
: Enables query batching. Defaults tofalse
.limit
: The maximum number of queries in a batch. If not set, the gateway does not limit the number of queries in a batch.
The retry configuration lets you define how a failing subgraph request gets handled. The subgraph request can fail if the subgraph service times out, returns an error code, or the rate limit is reached.
[gateway.retry]
enabled = true
min_per_second = 10
ttl = "1s"
retry_percent = 0.1
retry_mutations = false
The retries are based on a budget logic. Each successful request to the subgraph stores a deposit to the budget, and each failing request withdraws from the budget.
enabled
: Enables retries for the given subgraph. Defaults tofalse
.min_per_second
: How many retries are available per second, at a minimum. Defaults to10
.ttl
: Each successful request to the subgraph adds to the retry budget. This setting controls how long the budget remembers successful requests. Defaults to10s
.retry_percent
: The fraction of the successful requests budget that can be used for retries. Defaults to0.2
.retry_mutations
: Whether mutations should be retried at all. Enable this setting only if mutations are idempotent. Defaults tofalse
.
If you enable subgraph retries, they will execute with an exponential backoff. The first retry occurs after 100 milliseconds, the second after 200 milliseconds, the third after 400 milliseconds, and so on. The engine will add jitter to the times to prevent the thundering herd problem of having too many requests reaching the subgraph at once. A jitter multiplier between 0.0 to 2.0 is added to the retry backoff.
Read more on retries specific for a subgraph.
Read more about access logs to implement the required hooks.
[gateway.access_logs]
enabled = true
path = "/path/to/logs"
rotate = "daily"
mode = "blocking"
enabled
: Enables the access log writer.path
: Specifies the log directory.rotate
: Defines a rotation strategy, after which a new log file is created, and the previous one gets archived; options includenever
,minutely
,hourly
, ordaily
.rotate.size
: Defines a size-based rotation strategy. The value is in bytes; the log file rotates when it reaches or exceeds this limit.mode
: Specifies how the system behaves when the log queue is full and the writer can't write to the file quickly enough. Options includeblocking
, which blocks the caller, ornon_blocking
, which returns an error while sending the data back to the caller.
The current log filename will be access.log
, saved to the specified path. If you enable rotation, the current log file is access.log
, and archived files will include a timestamp suffix. The timestamp is from the moment the logger started writing to the file.
Enable CSRF protection if the graph is accessible over the internet with a browser.
If enabled, you must provide a special header x-grafbase-csrf-protection: 1
in every request not OPTIONS
. The server returns 403 Forbidden
if the header is not found.
[csrf]
enabled = true
enabled
: Enables CSRF protection. Defaults tofalse
.
Configure CORS to prevent unauthorized browser requests.
[cors]
allow_credentials = false
allow_origins = ["https://app.grafbase.com"]
max_age = "60s"
allow_methods = ["GET", "POST"]
allow_headers = ["Content-Type"]
expose_headers = ["Access-Control-Allow-Origin"]
allow_private_network = false
allow_credentials
: Enables or disables credential sending. Defaults tofalse
.allow_origins
: List of allowed domains or “any”. Defaults to no domains if CORS is enabled.max_age
: Duration for caching preflightOPTIONS
request results. Default: none.allow_methods
: List of allowed HTTP methods or “any”. Defaults to none if CORS is enabled.allow_headers
: List of allowed headers or “any”. Defaults to no headers if CORS is enabled.expose_headers
: Headers a preflight request can return to the client. Default: no headers if CORS is enabled.allow_private_network
: Allows private network requests. Defaults tofalse
.
[tls]
certificate = "/path/to/cert.pem"
key = "/path/to/key.pem"
The gateway can serve HTTPS traffic without a reverse proxy. If no tls
section is defined, the server operates in plain HTTP mode.
certificate
: Path to the certificate file in PEM format.key
: Path to the private key file in PEM format.
[operation_limits]
depth = 10
height = 12
aliases = 5
root_fields = 6
complexity = 2
Read more about Operation Limits.
[trusted_documents]
enabled = true
bypass_header_name = "my-header-name"
bypass_header_value = "my-secret-value"
Read more about Trusted Documents.
The gateway supports JWT authentication, configurable through the TOML file:
[authentication.providers.jwt]
name = "my-authenticator"
[authentication.providers.jwt.jwks]
url = "https://example.com/.well-known/jwks.json"
issuer = "example.com"
audience = "my-project"
poll_interval = 60
[authentication.providers.jwt.header]
name = "Authorization"
value_prefix = "Bearer "
Read more about Federated Authorization.
Define headers as rules executed in order. Use forward
, insert
, rename_duplicate
or remove
to manage headers sent to subgraphs:
[[headers]]
rule = "forward"
name = "authorization"
[[headers]]
rule = "forward"
name = "x-custom-header"
rename = "y-custom-header"
[[headers]]
rule = "forward"
name = "x-possible-empty"
default = "default-value"
You can forward multiple headers using a regular expression.
The headers the engine is matching against are in lowercase, so either write the regular expression all in lowercase, or provide a flag in the expression to match lower and uppercase letters.
[[headers]]
rule = "forward"
pattern = "^x-custom*"
Insert header values with the insert
rule:
[[headers]]
rule = "insert"
name = "authorization"
value = "Bearer secret-token"
You can insert headers values from environment variables:
[[headers]]
rule = "insert"
name = "authorization"
value = "Bearer {{ env.AUTHORIZATION_TOKEN }}"
Use the remove
rule to prevent forwarding of selected headers when using patterns:
[[headers]]
rule = "remove"
name = "x-custom-secret"
The rename_duplicate
rule is to forward the defined header value and a copy of the value with the given rename to the subgraphs.
[[headers]]
rule = "rename_duplicate"
name = "x-custom-value"
default = "the value was missing"
rename = "y-custom-value"
This will forward two headers to the subgraph, both with the same value. One with the name x-custom-value
and the other with the name y-custom-value
. If the header x-custom-value
is missing in the request, by defining the default
the gateway will create two headers with the given default. If the default is omitted, the gateway will not forward the header or create a duplicate.
The following headers cannot be controlled through header rules:
Accept
Accept-Charset
Accept-Encoding
Accept-Ranges
Connection
Content-Length
Content-Type
Keep-Alive
Proxy-Authenticate
Proxy-Authorization
TE
Trailer
Transfer-Encoding
Upgrade
Read more on subgraph header rules.
Enable the gateway to expose a health check endpoint:
[health]
enabled = true
listen = "0.0.0.0:9668"
path = "/health"
enabled
: Whether to expose the health check endpoint. Defaults totrue
.path
: The request path for the endpoint. Defaults to"/health"
.listen
: Address and port to bind for health requests. If omitted, uses the main GraphQL endpoint's socket and port.
The health check endpoint returns a status code 200 with with a body of {"status": "healthy"}
when the gateway is in a healthy state. It will reply with a status code 503 when it can determine that the gateway is not healthy.
The TLS configuration from tls
in your configuration also applies to the health check endpoint.
You can define settings per subgraph. These settings will either add to or override the global settings.
Define per-subgraph options such as the WebSocket URL and timeouts:
[subgraphs.products]
websocket_url = "https://example.com/"
timeout = "4s"
You can override the URL for a subgraph. If set, the gateway will use the provided URL instead of the one from the schema registry.
[subgraphs.products]
url = "https://example.com/graphql"
Define header rules per subgraph. They execute after the global rules.
[[subgraphs.products.headers]]
rule = "forward"
name = "content-type"
Read more on global header rules.
You can define rate limit per subgraph, which is checked before issuing a request. Read more about rate limiting.
[subgraphs.products.rate_limit]
limit = 100
duration = "10s"
You can hot-reload the subgraph rate limit and duration settings.
A rate limit global rate limit.
You can override the retry budget for the specific subgraph.
[subgraphs.products.retry]
enabled = true
min_per_second = 10
ttl = "1s"
retry_percent = 0.1
retry_mutations = false
Read more about retries.
The server is written in Rust and consists of several components:
axum
web server- Tokio runtime for async operations, running one worker per CPU core
- Rustls for encryption, backed by the ring and BoringTLS libraries
- Reqwest HTTP client, using Hyper and Rustls for encryption
In hybrid mode, the gateway validates the access token and fetches the latest federated graph from the CDN every ten seconds. It updates the graph and replaces the GraphQL engine as needed.
Example compose.yaml
:
version: '3'
services:
grafbase:
image: ghcr.io/grafbase/gateway:latest
restart: always
volumes:
- ./grafbase.toml:/etc/grafbase.toml
environment:
GRAFBASE_GRAPH_REF: 'graph-ref@branch'
GRAFBASE_ACCESS_TOKEN: 'ACCESS_TOKEN_HERE'
ports:
- '5000:5000'