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 organization 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 organization access token can be created in the account settings, under "Access Tokens", 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 to false.
[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 to 127.0.0.1:5000.

You can define settings for the gateway server in this section:

[gateway] timeout = "30s"
  • timeout changes the timeout for slow requests and responses. Default: "30s".

Rate limiting

[gateway.rate_limit.global] limit = 100 duration = "10s"

Read more from the rate limiting documentation/.

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 to false.

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 to false.
  • allow_origins is either a list of domains the server can be accessed from, or “any” which allows access from any domain. If cors settings are enabled, defaults to no domains allowed.
  • max_age indicates how long the results of a preflight OPTIONS 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 enable GET and POST 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 to false.
[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 are defined as rules executed in the order written in the configuration. The rules can either forward, insert or remove headers to the subgraphs. Header names are expected to be written in lowercase.

The forwarding can take a single value of the request, optionally rename it and also optionally add a static value if the header is missing:

[[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 by using a regular expression pattern. The headers the engine is matching against are all 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*"

The Grafbase Gateway can insert header values with the insert rule.

[[headers]] rule = "insert" name = "authorization" value = "Bearer secret-token"

You can take the inserted header value from the environment variables. The environment variables must be set before starting the gateway.

[[headers]] rule = "insert" name = "authorization" value = "Bearer {{ env.AUTHORIZATION_TOKEN }}"

A special remove rule is meant to be used together with pattern forwarding. If you forward all headers with pattern ^x-custom*, but do not want to forward a selected set of the matches:

[[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, with the header names of x-custom-value and y-custom-value. If the header x-custom-value was 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

Certain options can be defined per subgraph. The websocket URL can be something else than the one responding to HTTP requests:

[subgraphs.products] websocket_url = "https://example.com/"

You can also define a timeout for requests to a specific subgraph. The default is "30s":

[subgraphs.products] timeout = "4s"

You can define header rules per subgraph. They are executed after the global rules, and the same syntax is allowed as with the global header rules.

[[subgraphs.products.headers]] rule = "forward" name = "content-type"

You can define rate limiting per subgraph and its checked before issuing any request to it. Read more from the rate limiting documentation/.

[[subgraphs.products.rate_limit]] limit = 100 duration = "10s"

The gateway exposes an endpoint for health checks.

[health] enabled = true listen = "0.0.0.0:9668" path = "/health"
  • enabled (default: true): whether the health check endpoint is exposed at all.
  • path (default: "/health"): the request path the endpoint is exposed at
  • listen: if specified, an address and port to bind to and listen for health requests at path. Otherwise, the endpoint is on the same socket and port as the main GraphQL endpoint.

The health check endpoint returns a 200 response with bodies of the shape {"status": "healthy"} when the gateway is in a healthy state. It will reply with a 503 status when it can determine that the gateway is not healthy.

Note that the TLS configuration from tls in your configuration also applies to the health check endpoint.

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'
Was this page helpful?