跳到主要内容

a6-recipe-graphql-proxy

概览

APISIX 通过三个内置变量提供 GraphQL 支持,无需上游自行解析 GraphQL 请求,即可根据查询内容进行路由并应用策略:

变量描述初始值
graphql_name查询中的操作名称"getUser"
graphql_operation操作类型"query""mutation"
graphql_root_fields请求的顶级字段["user", "orders"]

APISIX 会从 Content-Typeapplication/jsonapplication/graphql 的 POST 请求,以及带有 query 参数的 GET 请求中自动提取这些变量。

适用场景

  • 将不同的GraphQL操作路由到不同的后端
  • 按操作类型应用不同的限流策略(查询与变更)
  • 限制特定消费者可以执行的操作
  • 使用 degraphql 将 REST 端点转换为 GraphQL 查询
  • 向GraphQL API添加安全层(身份认证、限流)

方法 A:按操作类型路由

使用 graphql_operation 变量,将 GraphQL 查询和变更操作路由到不同的后端。

将查询路由到只读副本,将变更路由到主副本

# Queries → read replica
a6 route create -f - <<'EOF'
{
"id": "graphql-queries",
"uri": "/graphql",
"vars": [["graphql_operation", "==", "query"]],
"upstream": {
"type": "roundrobin",
"nodes": { "graphql-read-replica:4000": 1 }
}
}
EOF

# Mutations → primary database
a6 route create -f - <<'EOF'
{
"id": "graphql-mutations",
"uri": "/graphql",
"vars": [["graphql_operation", "==", "mutation"]],
"upstream": {
"type": "roundrobin",
"nodes": { "graphql-primary:4000": 1 }
}
}
EOF

按操作名称路由

# Route the expensive "analytics" query to a dedicated backend
a6 route create -f - <<'EOF'
{
"id": "graphql-analytics",
"uri": "/graphql",
"vars": [["graphql_name", "==", "getAnalytics"]],
"priority": 10,
"upstream": {
"type": "roundrobin",
"nodes": { "analytics-backend:4000": 1 }
}
}
EOF

priority字段确保在通用/graphql路由之前匹配此路由。

方法 B:按操作类型限流

为查询与变更操作应用不同的限流策略。

# Queries: 1000 req/min
a6 route create -f - <<'EOF'
{
"id": "graphql-query-limited",
"uri": "/graphql",
"vars": [["graphql_operation", "==", "query"]],
"plugins": {
"key-auth": {},
"limit-count": {
"count": 1000,
"time_window": 60,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "graphql-backend:4000": 1 }
}
}
EOF

# Mutations: 100 req/min (more restrictive)
a6 route create -f - <<'EOF'
{
"id": "graphql-mutation-limited",
"uri": "/graphql",
"vars": [["graphql_operation", "==", "mutation"]],
"plugins": {
"key-auth": {},
"limit-count": {
"count": 100,
"time_window": 60,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Mutation rate limit exceeded"
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "graphql-backend:4000": 1 }
}
}
EOF

方法 C:按消费者限制操作

使用 consumer-restriction,仅允许特定消费者执行变更操作。

a6 route create -f - <<'EOF'
{
"id": "graphql-mutations-restricted",
"uri": "/graphql",
"vars": [["graphql_operation", "==", "mutation"]],
"plugins": {
"key-auth": {},
"consumer-restriction": {
"whitelist": ["admin-user", "service-account"],
"rejected_code": 403,
"rejected_msg": "Mutations not allowed for your account"
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "graphql-backend:4000": 1 }
}
}
EOF

方法 D:使用 degraphql 将 REST 转换为 GraphQL

degraphql 插件将 REST 端点转换为 GraphQL 查询,使 REST 客户端可以访问 GraphQL 后端。

1. 在路由上启用 degraphql

a6 route create -f - <<'EOF'
{
"id": "rest-to-graphql-users",
"uri": "/users/:id",
"methods": ["GET"],
"plugins": {
"degraphql": {
"query": "query getUser($id: ID!) { user(id: $id) { id name email } }",
"variables": ["id"]
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "graphql-backend:4000": 1 }
}
}
EOF

REST 客户端调用 GET /users/123,并收到 user(id: "123") 对应的 GraphQL 响应。

2. 静态查询(不使用变量)

a6 route create -f - <<'EOF'
{
"id": "rest-to-graphql-stats",
"uri": "/stats",
"methods": ["GET"],
"plugins": {
"degraphql": {
"query": "{ systemStats { cpu memory uptime } }"
}
},
"upstream": {
"type": "roundrobin",
"nodes": { "graphql-backend:4000": 1 }
}
}
EOF

声明式 GraphQL 配置

# apisix-graphql.yaml
routes:
- id: graphql-queries
uri: "/graphql"
vars: [["graphql_operation", "==", "query"]]
plugins:
key-auth: {}
limit-count:
count: 1000
time_window: 60
key_type: var
key: consumer_name
upstream:
type: roundrobin
nodes:
"graphql-read-replica:4000": 1

- id: graphql-mutations
uri: "/graphql"
vars: [["graphql_operation", "==", "mutation"]]
plugins:
key-auth: {}
limit-count:
count: 100
time_window: 60
key_type: var
key: consumer_name
consumer-restriction:
whitelist: ["admin-user", "service-account"]
upstream:
type: roundrobin
nodes:
"graphql-primary:4000": 1
a6 config diff -f apisix-graphql.yaml
a6 config sync -f apisix-graphql.yaml

注意事项

  • 请求体大小限制——APISIX 从请求体解析 GraphQL。默认最大请求体为 1 MiB,可通过 APISIX 配置中的 client_max_body_size 调整。过大的查询可能会被拒绝。
  • 仅支持单个操作——APISIX 只从请求中的第一个操作提取变量。包含多个操作的批量 GraphQL 查询不能用于路由匹配。
  • 不支持解析 WebSocket 订阅——内置 GraphQL 解析不支持基于 WebSocket 的订阅。APISIX 仍可代理 WebSocket 连接,但无法按 GraphQL 操作进行路由。
  • POST 请求的内容类型——GraphQL 解析支持 application/json(标准格式)和 application/graphql(请求体为原始查询)。其他内容类型不会被解析。
  • GET 请求——GraphQL 变量从 query URL 参数中读取,该参数应包含 URL 编码后的 GraphQL 查询字符串。
  • vars 匹配——路由的 vars 字段接受条件数组,每个条件的格式为 ["variable", "operator", "value"]。多个条件按逻辑与组合。
  • degraphql 限制——无论原始请求使用什么方法,插件都会向上游发送 Content-Type: application/json 的 POST 请求。variables 字段按名称将 URI 路径参数映射到 GraphQL 变量。
  • 重叠路由的优先级——当多条具有不同 vars 的路由都匹配 /graphql 时,使用 priority 字段控制匹配顺序;数值越高,优先级越高。

验证

# Test query routing
curl -X POST http://localhost:9080/graphql \
-H "Content-Type: application/json" \
-H "apikey: my-key" \
-d '{"query": "query getUser { user(id: 1) { name } }"}'

# Test mutation routing
curl -X POST http://localhost:9080/graphql \
-H "Content-Type: application/json" \
-H "apikey: my-key" \
-d '{"query": "mutation createUser { createUser(name: \"test\") { id } }"}'

# Test rate limiting (should 429 after exceeding limit)
for i in $(seq 1 1001); do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST http://localhost:9080/graphql \
-H "Content-Type: application/json" \
-H "apikey: my-key" \
-d '{"query": "{ users { id } }"}'
done

# Test REST-to-GraphQL
curl http://localhost:9080/users/123
# Returns GraphQL response for user(id: "123")

本文根据 api7/a6 仓库中的 a6-recipe-graphql-proxy/SKILL.md 生成。可在 AI Agent Skills 页面浏览全部 Skill。