Skip to main content
Version: 3.17

graphql-limit-count

Description#

The graphql-limit-count Plugin limits the rate of GraphQL requests using a fixed window algorithm. Unlike limit-count, which counts each request as a cost of 1, this Plugin uses the depth of the GraphQL query AST as the cost. This allows you to enforce stricter limits on deeply nested queries that are more expensive to process.

Only POST requests are supported. The Plugin accepts two content types:

  • application/json: request body must contain a query field with the GraphQL query string.
  • application/graphql: request body is the raw GraphQL query starting with query.

You may see the following rate limiting headers in the response:

  • X-RateLimit-Limit: the total quota
  • X-RateLimit-Remaining: the remaining quota
  • X-RateLimit-Reset: number of seconds left for the counter to reset

Attributes#

This Plugin shares the same schema as the limit-count Plugin. Refer to that page for the full attribute reference. Key attributes are listed below.

NameTypeRequiredDefaultValid valuesDescription
countinteger or stringFalse> 0The maximum allowed accumulated query AST depth within the time window. Required if rules is not configured.
time_windowinteger or stringFalse> 0The time interval in seconds for the rate limiting window. Required if rules is not configured.
key_typestringFalsevar["var", "var_combination", "constant"]The type of key. var treats key as an NGINX variable. var_combination combines multiple variables. constant uses key as a fixed value.
keystringFalseremote_addrThe key to count requests by.
rejected_codeintegerFalse503[200,...,599]HTTP status code returned when a request is rejected for exceeding the quota.
rejected_msgstringFalsenon-emptyResponse body returned when a request is rejected.
policystringFalselocal["local", "redis", "redis-cluster"]Counter storage policy. local stores the counter in memory on the current APISIX node. redis and redis-cluster share counters across instances.
allow_degradationbooleanFalsefalseWhen true, APISIX continues handling requests if the Plugin or its dependencies become unavailable.
show_limit_quota_headerbooleanFalsetrueWhen true, include X-RateLimit-Limit and X-RateLimit-Remaining headers in the response.
groupstringFalsenon-emptyGroup ID to share a single rate limiting counter across multiple routes.
redis_hoststringFalseAddress of the Redis node. Required when policy is redis.
redis_portintegerFalse6379[1,...]Port of the Redis node. Used when policy is redis.
redis_usernamestringFalseUsername for Redis ACL authentication. Used when policy is redis.
redis_passwordstringFalsePassword of the Redis node. Used when policy is redis or redis-cluster.
redis_sslbooleanFalsefalseWhen true, use SSL to connect to Redis. Used when policy is redis.
redis_ssl_verifybooleanFalsefalseWhen true, verify the Redis server SSL certificate. Used when policy is redis.
redis_databaseintegerFalse0>= 0The Redis database number. Used when policy is redis.
redis_timeoutintegerFalse1000[1,...]Redis timeout in milliseconds. Used when policy is redis or redis-cluster.
redis_cluster_nodesarray[string]FalseList of Redis cluster node addresses. Required when policy is redis-cluster.
redis_cluster_namestringFalseName of the Redis cluster. Required when policy is redis-cluster.
redis_cluster_sslbooleanFalsefalseWhen true, use SSL to connect to the Redis cluster. Used when policy is redis-cluster.
redis_cluster_ssl_verifybooleanFalsefalseWhen true, verify the Redis cluster server SSL certificate. Used when policy is redis-cluster.

Examples#

The examples below demonstrate how you can configure graphql-limit-count in different scenarios.

note
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')

Limit Requests by Query Depth per Client#

The following example demonstrates how to rate limit GraphQL requests based on the accumulated query AST depth per client IP address. A shallow query like { foo { bar } } (depth 2) consumes 2 out of the quota, while a deeply nested query like { foo { bar { baz { id } } } } (depth 4) consumes 4.

Create a Route with graphql-limit-count that allows a cumulative query depth of 10 per minute per client IP:

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "graphql-limit-count-route",
"uri": "/graphql",
"plugins": {
"graphql-limit-count": {
"count": 10,
"time_window": 60,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr",
"policy": "local"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'

Send a depth-4 GraphQL query:

curl -i "http://127.0.0.1:9080/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "query { foo { bar { baz { id } } } }"}'

You should receive an HTTP/1.1 200 OK response with the following headers:

X-RateLimit-Limit: 10
X-RateLimit-Remaining: 6

The depth-4 query consumed 4 out of the 10 quota. After the quota is exhausted within the time window, you will receive HTTP/1.1 429 Too Many Requests.

Share Quota Among APISIX Nodes with a Redis Server#

The following example demonstrates how to use a Redis-backed counter so that the rate limiting quota is shared across multiple APISIX instances.

curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "graphql-limit-count-route",
"uri": "/graphql",
"plugins": {
"graphql-limit-count": {
"count": 100,
"time_window": 60,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr",
"policy": "redis",
"redis_host": "127.0.0.1",
"redis_port": 6379
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'

Send a request to verify:

curl -i "http://127.0.0.1:9080/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "query { foo { bar } }"}'

You should receive an HTTP/1.1 200 OK response. The counter is now shared across all APISIX nodes connected to the same Redis instance.