Docs
Plugin HubTraffic ManagementGraphQL Proxy CacheOverview

graphql-proxy-cache

The graphql-proxy-cache plugin caches successful GraphQL query responses in memory or on disk. It supports GraphQL GET and POST requests and returns an Apisix-Cache-Status header that identifies a cache miss, hit, or bypass.

The cache key includes the plugin configuration version, request host, route ID, service ID, authenticated consumer identity, and complete GraphQL request data. Consumer isolation is enabled by default, preventing authenticated consumers from sharing cached responses. A GraphQL mutation bypasses the cache.

For GET requests, provide the GraphQL document in the query query parameter. POST requests can use a JSON body with a query field or an application/graphql body.

The plugin accepts only GET and POST requests. Other methods return HTTP 405, while unreadable, malformed, or invalid GraphQL requests return HTTP 400.

Examples

The examples use the public Countries GraphQL API and the same query throughout:

query {
  country(code: "US") {
    name
    capital
  }
}

Cache Responses on Disk

The default strategy stores cached responses under the configured disk cache path. Persist that path outside an ephemeral container when cached data should survive container replacement.

Create a route with a stable ID so the same value can be used by the purge example:

curl "http://127.0.0.1:9180/apisix/admin/routes/graphql-proxy-cache-route" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "uri": "/graphql",
    "plugins": {
      "graphql-proxy-cache": {}
    },
    "upstream": {
      "type": "roundrobin",
      "pass_host": "node",
      "scheme": "https",
      "nodes": {
        "countries.trevorblades.com:443": 1
      }
    }
  }'

Send the query and inspect the cache headers:

curl -i "http://127.0.0.1:9080/graphql" -X POST \
  -H "Content-Type: application/json" \
  -d '{"query":"query { country(code: \"US\") { name capital } }"}'

The first request should return Apisix-Cache-Status: MISS. Repeating the same request should return Apisix-Cache-Status: HIT. The response body should contain:

{
  "data": {
    "country": {
      "capital": "Washington D.C.",
      "name": "United States"
    }
  }
}

The Countries API sends its own long-lived Cache-Control header. For disk caching, that upstream header takes precedence over the gateway's fallback apisix.proxy_cache.cache_ttl value.

Purge a Disk Cache Entry

Expose the plugin's purge endpoint through a route protected by key-auth:

Create the cache operator:

curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "username": "cache-operator"
  }'

Add a key-auth credential to the consumer:

curl "http://127.0.0.1:9180/apisix/admin/consumers/cache-operator/credentials" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "id": "cache-operator-key-auth",
    "plugins": {
      "key-auth": {
        "key": "purge-key"
      }
    }
  }'

Create the protected purge route:

curl "http://127.0.0.1:9180/apisix/admin/routes/graphql-cache-purge" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "uri": "/apisix/plugin/graphql-proxy-cache/*",
    "plugins": {
      "key-auth": {},
      "public-api": {}
    }
  }'

Send the disk-cache request and save the generated key:

export CACHE_KEY="$(
  curl -sS -D - -o /dev/null "http://127.0.0.1:9080/graphql" -X POST \
    -H "Content-Type: application/json" \
    -d '{"query":"query { country(code: \"US\") { name capital } }"}' | \
  awk 'tolower($1) == "apisix-cache-key:" {gsub("\\r", "", $2); print $2}'
)"

The Admin API and ADC workflows use the stable route ID graphql-proxy-cache-route:

export CACHE_ROUTE_ID=graphql-proxy-cache-route

For an Ingress Controller deployment, set the generated route name according to the resource used:

export CACHE_ROUTE_NAME=aic_graphql-proxy-cache_0-0

Retrieve the corresponding gateway route ID through the Admin API:

export CACHE_ROUTE_ID="$(
  curl -fsS "http://127.0.0.1:9180/apisix/admin/routes" \
    -H "X-API-KEY: ${ADMIN_API_KEY}" | \
  jq -er --arg name "$CACHE_ROUTE_NAME" '
    .list[].value
    | select(.name == $name)
    | .id
  '
)"

Purge the cached response:

curl -i "http://127.0.0.1:9080/apisix/plugin/graphql-proxy-cache/disk/${CACHE_ROUTE_ID}/${CACHE_KEY}" \
  -X PURGE \
  -H "apikey: purge-key"

The first purge should return 200 OK. Repeating the request should return 404 Not Found, confirming that the disk entry no longer exists.

Responses with a Vary header can produce multiple cache variants. A successful disk purge removes the targeted entry but does not guarantee that every variant was removed.

Cache Responses in Memory

Update the existing cache route to use in-memory caching. Define the cache zone in the static configuration before applying this example.

curl "http://127.0.0.1:9180/apisix/admin/routes/graphql-proxy-cache-route" -X PUT \
  -H "X-API-KEY: ${ADMIN_API_KEY}" \
  -d '{
    "uri": "/graphql",
    "plugins": {
      "graphql-proxy-cache": {
        "cache_strategy": "memory",
        "cache_zone": "memory_cache",
        "cache_ttl": 10
      }
    },
    "upstream": {
      "type": "roundrobin",
      "pass_host": "node",
      "scheme": "https",
      "nodes": {
        "countries.trevorblades.com:443": 1
      }
    }
  }'

❶ cache_strategy: set to memory to store responses in memory.

❷ cache_zone: set to the name of a configured in-memory cache zone.

❸ cache_ttl: set the in-memory cache lifetime in seconds.

Send the GraphQL request twice. The first response should report MISS, and the second should report HIT. Wait at least ten seconds and send it again; the response should report MISS because the in-memory entry expired.