Skip to content

Lua API

gqlc render generates every call below. Read this page if you write the nginx configuration by hand, or if you already have an init_by_lua ([nginx] init = false).

Function Phase
require("gqlgw").init(opts) init_by_lua loads libgql.so and the schema, configures the cache and the metrics
require("gqlgw").serve() content_by_lua of the GraphQL location handles one GraphQL request
require("gqlgw").log() log_by_lua of the GraphQL location request metrics, $gql_* log variables
require("gqlgw").metrics() content_by_lua Prometheus text
require("gqlgw").purge() content_by_lua POST ?source=<name>: purge a source

init(opts)

opts is either the path of the gqlgw.lua written by gqlc render (recommended), or a table:

require("gqlgw").init("/tmp/gqlgw/gqlgw.lua")

-- or, by hand:
require("gqlgw").init({
    schema = "/etc/gqlgw/schema.gqlb",        -- routes: /etc/gqlgw/schema.routes.lua
    lib = "/usr/local/lib/gqlgw/libgql.so",
    introspection = true,
    debug_headers = false,
    strict_path_args = true,
    cache = {
        shm = "gqlgw_cache",                  -- a lua_shared_dict; omit for no L2
        l2_ttl = 5,
        memcached = { host = "127.0.0.1", port = 11211, timeout = 50, pool_size = 64 },
        private_header = "Authorization",
    },
    metrics = { shm = "gqlgw_metrics", exclude_operations = { "^IntrospectionQuery$" } },
    max_query_bytes = 65536, max_depth = 32, max_rounds = 16, max_fetches = 1000,
})
Key Default gqlgw.toml equivalent
schema, routes, lib required, <schema>.routes.lua, system library [gateway]
introspection, debug_headers, strict_path_args true, false, true [gateway]
max_body_bytes 1048576 [gateway] max_body
max_query_bytes, max_tokens, max_depth, max_errors 65536, 20000, 32, 100 [limits]
max_plan_fields, max_memory 20000, 16 MiB [limits]
max_rounds, max_fetches, concurrency, max_response_bytes 16, 1000, 64, 16 MiB [limits]
cache_negative_ttl 5 [cache] negative_ttl
cache none [cache], [cache.memcached]
metrics none (no metrics) [metrics]
sources not checked the list of [sources.*]: init fails if the schema uses another one
sources_prefix "/_src/" —

Hand-written locations

Without gqlc render, you write the locations yourself. The contract:

  • the GraphQL location declares $gqlgw_path (set $gqlgw_path "";);
  • one location /_src/<source>/ per source, which sends $gqlgw_path upstream as is;
  • these locations must be internal, clear Accept-Encoding, and choose the client headers they forward.
# gqlgw: hand-written OpenResty configuration.
#
# The recommended way is gqlgw.toml + `gqlc render`, which generates the
# equivalent of this file (see README.md and examples/docker/gqlgw.toml).
# This example shows what a configuration without it looks like:
#
#   gqlc compile -o /etc/gqlgw/schema.gqlb schema.graphqls   # also writes schema.routes.lua
#   openresty -c /etc/gqlgw/nginx.conf

worker_processes auto;
error_log logs/error.log warn;

events {
    worker_connections 4096;
}

http {
    lua_package_path "/opt/gqlgw/lua/?.lua;/opt/gqlgw/lua/?/init.lua;;";

    # L2 cache (per node). Optional but recommended.
    lua_shared_dict gqlgw_cache 128m;

    init_by_lua_block {
        require("gqlgw").init({
            schema = "/etc/gqlgw/schema.gqlb",          -- routes: /etc/gqlgw/schema.routes.lua
            lib = "/opt/gqlgw/lib/libgql.so",
            cache = {
                shm = "gqlgw_cache",
                l2_ttl = 5,                             -- seconds, cap for shm entries
                memcached = { host = "127.0.0.1", port = 11211, timeout = 50, pool_size = 64 },
                private_header = "Authorization",       -- identity of @cache(scope: "PRIVATE")
            },
            introspection = true,
            max_query_bytes = 65536,
            max_depth = 32,
            max_fetches = 1000,                         -- upstream requests per GraphQL request
            max_rounds = 16,                            -- depth of fetch chains
            strict_path_args = true,                    -- reject "/", "." and ".." in path segments
            debug_headers = false,                      -- X-GQL-Rounds, X-GQL-Subrequests, X-GQL-Cache
        })
    }

    upstream users_svc {
        server 10.0.0.10:8080;
        keepalive 64;
    }
    upstream posts_svc {
        server 10.0.0.20:8080;
        keepalive 64;
    }

    log_format gql '$remote_addr "$request" $status $request_time '
                   'rounds=$gql_rounds sub=$gql_subrequests cache=$gql_cache';

    server {
        listen 8080;

        location = /graphql {
            set $gqlgw_path "";          # declares the variable used by the /_src/ locations
            set $gql_rounds "-";
            set $gql_subrequests "-";
            set $gql_cache "-";
            client_max_body_size 1m;
            client_body_buffer_size 1m;  # keep request bodies in memory
            access_log logs/graphql.log gql;
            content_by_lua_block { require("gqlgw").serve() }
            log_by_lua_block { require("gqlgw").log() }
        }

        # One internal location per @fetch(source: "...").
        # The upstream path rendered from the SDL templates arrives in $gqlgw_path,
        # already URI-escaped: nginx sends it verbatim.
        location /_src/users/ {
            internal;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Accept-Encoding "";      # subrequests inherit client headers
            proxy_set_header Host users.internal;
            proxy_connect_timeout 1s;
            proxy_read_timeout 3s;
            proxy_pass http://users_svc/api/v1$gqlgw_path;
        }

        location /_src/posts/ {
            internal;
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_set_header Accept-Encoding "";
            proxy_set_header Host posts.internal;
            proxy_connect_timeout 1s;
            proxy_read_timeout 3s;
            proxy_pass http://posts_svc$gqlgw_path;
        }

        # Cache purge (one source): curl -X POST 'http://127.0.0.1:8080/_gqlgw/purge?source=users'
        location = /_gqlgw/purge {
            allow 127.0.0.1;
            deny all;
            content_by_lua_block { require("gqlgw").purge() }
        }
    }
}

The TOML configuration remains the recommended way: it generates these locations with their safeguards (allowlist, Host, TLS, Content-Type of bodies, upstream timing).