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
}
}
}'services:
- name: graphql-proxy-cache
labels:
docs-example: graphql-proxy-cache
routes:
- id: graphql-proxy-cache-route
name: graphql-proxy-cache
uris:
- /graphql
plugins:
graphql-proxy-cache: {}
upstream:
type: roundrobin
scheme: https
pass_host: node
nodes:
- host: countries.trevorblades.com
port: 443
weight: 1Preview changes owned by this example and confirm that the diff contains no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=graphql-proxy-cacheSynchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=graphql-proxy-cacheapiVersion: v1
kind: Service
metadata:
namespace: aic
name: countries-graphql
spec:
type: ExternalName
externalName: countries.trevorblades.com
ports:
- name: https
port: 443
targetPort: 443
---
apiVersion: apisix.apache.org/v1alpha1
kind: BackendTrafficPolicy
metadata:
namespace: aic
name: countries-graphql-https
spec:
targetRefs:
- name: countries-graphql
kind: Service
group: ""
sectionName: https
passHost: node
scheme: https
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: graphql-proxy-cache
spec:
plugins:
- name: graphql-proxy-cache
config: {}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: graphql-proxy-cache
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /graphql
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: graphql-proxy-cache
backendRefs:
- name: countries-graphql
port: 443apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: countries-graphql
spec:
ingressClassName: apisix
scheme: https
passHost: node
externalNodes:
- type: Domain
name: countries.trevorblades.com
port: 443
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: graphql-proxy-cache
spec:
ingressClassName: apisix
http:
- name: graphql-proxy-cache
match:
paths:
- /graphql
upstreams:
- name: countries-graphql
plugins:
- name: graphql-proxy-cache
enable: true
config: {}Apply the configuration:
kubectl apply -f graphql-proxy-cache-ic.yamlSend 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": {}
}
}'consumers:
- username: cache-operator
labels:
docs-example: graphql-cache-purge
credentials:
- name: cache-operator-key-auth
type: key-auth
config:
key: purge-key
services:
- name: graphql-cache-purge
labels:
docs-example: graphql-cache-purge
routes:
- id: graphql-cache-purge
name: graphql-cache-purge
uris:
- /apisix/plugin/graphql-proxy-cache/*
plugins:
key-auth: {}
public-api: {}
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1The service upstream satisfies the ADC service schema. The public-api plugin handles purge requests inside the gateway, so they are not proxied to this upstream.
Preview changes owned by this example and confirm that the diff contains no unintended updates or deletions:
adc diff -f purge-adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=graphql-cache-purgeSynchronize the reviewed service and consumer configurations:
adc sync -f purge-adc.yaml \
--include-resource-type service \
--include-resource-type consumer \
--label-selector docs-example=graphql-cache-purgeapiVersion: apisix.apache.org/v1alpha1
kind: Consumer
metadata:
namespace: aic
name: cache-operator
spec:
gatewayRef:
name: apisix
credentials:
- type: key-auth
name: cache-operator-key-auth
config:
key: purge-key
---
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: graphql-cache-purge
spec:
plugins:
- name: key-auth
config: {}
- name: public-api
config: {}
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: aic
name: graphql-cache-purge
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: PathPrefix
value: /apisix/plugin/graphql-proxy-cache/
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: graphql-cache-purgeapiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: cache-operator
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: purge-key
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: graphql-cache-purge
spec:
ingressClassName: apisix
http:
- name: graphql-cache-purge
match:
paths:
- /apisix/plugin/graphql-proxy-cache/*
plugins:
- name: key-auth
enable: true
- name: public-api
enable: trueApply the configuration:
kubectl apply -f graphql-cache-purge-ic.yamlSend 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-routeFor an Ingress Controller deployment, set the generated route name according to the resource used:
export CACHE_ROUTE_NAME=aic_graphql-proxy-cache_0-0export CACHE_ROUTE_NAME=aic_graphql-proxy-cache_graphql-proxy-cacheRetrieve 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
}
}
}'services:
- name: graphql-proxy-cache
labels:
docs-example: graphql-proxy-cache
routes:
- id: graphql-proxy-cache-route
name: graphql-proxy-cache
uris:
- /graphql
plugins:
graphql-proxy-cache:
cache_strategy: memory
cache_zone: memory_cache
cache_ttl: 10
upstream:
type: roundrobin
scheme: https
pass_host: node
nodes:
- host: countries.trevorblades.com
port: 443
weight: 1Preview the update:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=graphql-proxy-cacheSynchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=graphql-proxy-cacheUpdate the existing plugin configuration:
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
namespace: aic
name: graphql-proxy-cache
spec:
plugins:
- name: graphql-proxy-cache
config:
cache_strategy: memory
cache_zone: memory_cache
cache_ttl: 10Update the existing route:
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: graphql-proxy-cache
spec:
ingressClassName: apisix
http:
- name: graphql-proxy-cache
match:
paths:
- /graphql
upstreams:
- name: countries-graphql
plugins:
- name: graphql-proxy-cache
enable: true
config:
cache_strategy: memory
cache_zone: memory_cache
cache_ttl: 10Apply the complete updated resource:
kubectl apply -f graphql-proxy-cache-ic.yaml❶ 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.