Self-Hosted Gateway
The Grafbase Gateway provides a way to run a federated graph on premises in your own infrastructure. The gateway runs either by polling the latest graph from the Grafbase API, or by passing the federated schema which allows running the gateway without internet connectivity
The Grafbase Gateway can be installed by running the following command in a terminal:
curl -fsSL https://grafbase.com/downloads/gateway | bash
In this mode, the gateway fetches the current federated graph from the Grafbase platform. You create a federated graph in the Grafbase API, publish the subgraphs and the gateway always has the current graph running.
The gateway is started in hybrid mode by providing the graph ref and an access token:
GRAFBASE_ACCESS_TOKEN=token ./grafbase-gateway \
--config grafbase.toml \
--graph-ref graph@branch
Here the graph-ref
points to a graph created in the Grafbase API, and the branch to a name of a branch in that graph or if left empty, the production branch of the graph is used.
The access token is created in the API in your organization settings, and must have at least the permission to read a graph.
The gateway polls for graph changes every ten seconds.
In this mode, we never call the Grafbase API, and you have to provide the federated graph SDL as a file.
The gateway is started in self-hosted mode by providing the schema argument:
./grafbase-gateway \
--config /path/to/grafbase.toml \
--schema /path/to/federated-schema.graphql \
--listen-address 127.0.0.1:4000
The configuration is defined using the TOML file format, and has the following settings available:
[graph]
path = "/graphql"
introspection = false
path
is the path in the URL where the GraphQL API is available. Defaults to/graphql
.introspection
lets you to decide if GraphQL introspection is available or not. Defaults tofalse
.
[network]
listen_address = "127.0.0.1:5000"
listen_address
changes the address the local bound to. Can be set in the configuration or through the command line interface with-listen-address
. Can be either IPv4 or IPv6 address. Defaults to127.0.0.1:5000
.
The CSRF protection is needed if the graph is accessible over the internet with a browser. Combined together with the CORS settings, it lets you to decide how and from which address a browser can access the graph.
If enabled, a special header x-grafbase-csrf-protection: 1
is required in every request
that is not OPTIONS
. If the header is not found, the server returns 403 Forbidden
.
This is not needed if the server is not accessed through a browser.
[csrf]
enabled = true
enabled
enables the CSRF protection. Defaults tofalse
.
The CORS settings together with the CSRF protection are to prevent requests coming from unknown sources. The settings are only needed if the server is accessed through a browser.
[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
either enables or disabled credentials to be sent to the server. Defaults tofalse
.allow_origins
is either a list of domains the server can be accessed from, or “any” which allows access from any domain. Ifcors
settings are enabled, defaults to no domains allowed.max_age
indicates how long the results of a preflightOPTIONS
request can be cached. Defaults to none.allow_methods
gives a list of HTTP methods allowed to use when accessing the server. Can be either a list of HTTP methods as strings, or “any”. Defaults to none if CORS settings are enabled. A good default is to enableGET
andPOST
to have all the features available from the gateway.allow_headers
is a list of headers allowed to be sent to the endpoint. If the cors settings are enabled, the default is no headers allowed. Can be set either to a list of headers, or “any” which allows any headers to be sent.expose_headers
lists the headers a preflight request can sent back to the client. If the CORS settings are enabled, defaults to no headers allowed. The setting can be a list of headers, or “any” for any header to be exposed.allow_private_network
allows requests from a private network outside of the internet. Defaults tofalse
.
[tls]
certificate = "/path/to/cert.pem"
key = "/path/to/key.pem"
In some cases you might not want to run a reverse proxy such as nginx in front of the gateway. The gateway can serve HTTPS traffic by itself. For encryption we’re using the rustls
crate, backed by the BoringSSL
library. If the tls
section is not defined in the configuration, the server runs 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
See more details from the Operation Limits documentation.
The gateway currently supports JWT authentication, which can be set through the configuration:
[[authentication.providers]]
[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 "
See more details from the Federated Authorization documentation.
The headers can either be static or forwarded from the client.
[headers.Authentication]
value = "Bearer asdf"
[headers.Content-Type]
forward = "content-type"
value
is a static header valueforward
is a name of the header passed to the subgraphs from the request
Header values can be defined in environment variables with a special syntax:
[headers.Authentication]
value = "Bearer {{ env.BEARER }}"
The environment variables must be set before starting the gateway.
Certain options can be defined per subgraph.
[subgraphs.products]
websocket_url = "https://example.com/"
headers = {
"Content-Type" = { forward = "Content-Type },
"Authentication" = { value = "Bearer {{ env.AUTHENTICATION_TOKEN }}" }
}
websocket_url
The URL to use for GraphQL websocket calls.headers
per-subgraph header overrides.
Uses an async scheduler that periodically runs tasks to export batches of spans.
# optional: true
[telemetry]
# the name of the application/service
# https://opentelemetry.io/docs/specs/semconv/resource/#service
# optional: false
service_name = "grafbase-gateway"
# optional: true
[telemetry.tracing]
# default: false
# optional: true
enabled = true
# sample a given fraction of traces (0..=1)
## default: 0.15 (15% of traces will be exported)
## optional: true
sampling = 1
# filter that controls the interest on spans to be exported
# https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html
# default: grafbase=info,off (only spans with *target=grafbase* with level info are to be exported)
# optional: true
filter = grafbase=debug,hyper=debug,off
# spans collection
# optional: true
[telemetry.tracing.collect]
# the maximum events per span before discarding
# default: 128
# optional: true
max_events_per_span = 128
# the maximum attributes per span before discarding
# default: 128
# optional: true
max_attributes_per_span = 128
# the maximum links per span before discarding
# default: 128
# optional: true
max_links_per_span = 128
# the maximum attributes per event before discarding
# default: 128
# optional: true
max_attributes_per_event = 128
# the maximum attributes per link before discarding
# default: 128
# optional: true
max_attributes_per_link = 128
# OTLP exporter
# https://opentelemetry.io/docs/specs/otel/protocol/exporter/
# optional: true
[telemetry.tracing.exporters.otlp]
# default: false
# optional: true
enabled = true
# optional: false
endpoint = "http://localhost:1234"
# grpc or http are the available protocols
# default: grpc
# optional: true
protocol = "grpc"
# export request timeout in seconds
# default: 60
# optional: true
timeout = 120
# otlp batch export configuration
# optional: true
[telemetry.tracing.exporters.otlp.batch_export]
# the delay, in seconds, between two consecutive processing of batches
# default: 5
# optional: true
scheduled_delay = 10
# the maximum queue size to buffer spans for delayed processing. If the
# queue gets full it drops the spans
# default: 2048
# optional: true
max_queue_size = 10
# the maximum number of spans to process in a single batch. If there are
# more than one batch worth of spans then it processes multiple batches
# of spans one batch after the other without any delay.
# default: 512
# optional: true
max_export_batch_size = 10
# maximum number of concurrent exports
# default: 1
# optional: true
max_concurrent_exports = 10
# tls configuration to use on grpc export requests
# optional: true
[telemetry.tracing.exporters.otlp.grpc.tls]
# domain name against which to verify the server's TLS certificate
# optional: true
domain_name = "my_domain"
# path to the pem encoded private key of the certificate.
# It can be an absolute path or relative to the binary execution path.
# optional: true
key = "/certs/grafbase.key"
# path to the X509 Certificate, in pem format, that represents the client identity to present to the server
# optional: true
cert = "/certs/grafbase.crt"
# path to the X509 CA Certificate, in pem format, against which to verify the server's TLS certificate
# optional: true
ca = "/certs/ca.crt"
# headers to send on grpc export requests.
# values need to be valid ascii encoded strings
# optional: true
[telemetry.tracing.exporters.otlp.grpc.headers]
header1 = "header1"
# headers to send on http export requests.
# values need to be valid ascii encoded strings
# optional: true
[telemetry.tracing.exporters.otlp.http.headers]
header1 = "header1"
# prints to stdout the spans to export
# optional: true
[exporters.stdout]
# optional: true
enabled = true
# export request timeout in seconds
# default: 60
# optional: true
timeout = 10
# optional: true
[exporters.stdout.batch_export]
# same as grpc
The server is written in Rust, and consists from the following components:
- The latest
axum
web server - Tokio runtime for async operation. Runs in multi-threaded mode and has one worker per detected CPU core.
- Rustls for encryption, backed by the ring and BoringTLS libraries.
- Reqwest HTTP client, backed by the Hyper HTTP client together with rustls, ring and BoringTLS for encryption.
When the server is started in hybrid mode, it validates the access token and tries to fetch the latest federated graph from our CDN. It does a request every ten seconds, but doesn’t fetch anything if we detect no changes to be made in the graph. When composing a new or existing subgraph successfully, the graph is updated and the next call to the API fetches it, serves all the existing requests and replaces the GraphQL engine with a new version.
An example docker-compose.yml:
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'