a7-recipe-graphql-proxy
Overview
API7 EE can route and protect GraphQL traffic by using GraphQL variables from the parsed request body:
graphql_name: operation name, such asgetUser.graphql_operation: operation type, such asqueryormutation.graphql_root_fields: top-level fields requested by the operation.
Use the current a7 service-backed route model:
- Create services for the GraphQL backends.
- Create routes with
pathsandservice_id. - Add GraphQL
varsand plugins througha7 route create/update -fpayloads.
When to Use
- Route read-only GraphQL queries and mutations to different backends.
- Apply tighter limits to expensive operations.
- Expose REST-style paths that are translated to GraphQL with
degraphql. - Protect sensitive GraphQL operations with auth and access-control plugins.
Approach A: Operation-Based Routing
Create separate services for read and write GraphQL traffic.
a7 service create -g prod-group -f - <<'EOF'
{
"id": "gql-read-service",
"name": "gql-read-service",
"upstream": {
"type": "roundrobin",
"nodes": [
{"host": "gql-read-replica", "port": 4000, "weight": 1}
]
}
}
EOF
a7 service create -g prod-group -f - <<'EOF'
{
"id": "gql-write-service",
"name": "gql-write-service",
"upstream": {
"type": "roundrobin",
"nodes": [
{"host": "gql-primary-db", "port": 4000, "weight": 1}
]
}
}
EOF
Route GraphQL queries to the read service:
a7 route create -g prod-group -f - <<'EOF'
{
"id": "gql-queries",
"name": "gql-queries",
"paths": ["/graphql"],
"service_id": "gql-read-service",
"vars": [["graphql_operation", "==", "query"]]
}
EOF
Route GraphQL mutations to the write service:
a7 route create -g prod-group -f - <<'EOF'
{
"id": "gql-mutations",
"name": "gql-mutations",
"paths": ["/graphql"],
"service_id": "gql-write-service",
"vars": [["graphql_operation", "==", "mutation"]]
}
EOF
Use route priorities if a generic /graphql route overlaps with more specific
GraphQL operation routes.
Approach B: Per-Operation Rate Limiting
Apply stricter limits to mutation traffic. Include the service_id in update
payloads so the route remains bound to the intended service.
a7 route update gql-mutations -g prod-group -f - <<'EOF'
{
"service_id": "gql-write-service",
"plugins": {
"key-auth": {},
"limit-count": {
"count": 50,
"time_window": 60,
"key_type": "var",
"key": "consumer_name",
"rejected_code": 429,
"rejected_msg": "Mutation quota exceeded"
}
}
}
EOF
Create consumers and credentials separately:
a7 consumer create -g prod-group --username frontend-app
a7 credential create -g prod-group --consumer frontend-app --plugins-json '{"key-auth":{"key":"frontend-secret"}}'
Approach C: REST-to-GraphQL with degraphql
The degraphql plugin maps a REST-style path to a GraphQL query on the backend.
a7 service create -g prod-group -f - <<'EOF'
{
"id": "graphql-engine-service",
"name": "graphql-engine-service",
"upstream": {
"type": "roundrobin",
"nodes": [
{"host": "graphql-engine", "port": 8080, "weight": 1}
]
}
}
EOF
a7 route create -g prod-group -f - <<'EOF'
{
"id": "rest-bridge-user",
"name": "rest-bridge-user",
"paths": ["/api/users/:id"],
"methods": ["GET"],
"service_id": "graphql-engine-service",
"plugins": {
"degraphql": {
"query": "query getUser($id: ID!) { user(id: $id) { name email profile { bio } } }",
"variables": ["id"]
}
}
}
EOF
Approach D: Restrict Mutations
Use consumer-restriction to limit mutation routes to selected consumers or
consumer groups.
a7 route update gql-mutations -g prod-group -f - <<'EOF'
{
"service_id": "gql-write-service",
"plugins": {
"key-auth": {},
"consumer-restriction": {
"type": "consumer_name",
"whitelist": ["admin-client", "system-client"],
"rejected_code": 403,
"rejected_msg": "Only approved clients can perform mutations"
}
}
}
EOF
Declarative Management Notes
a7 config sync can manage services and normal service-backed routes. GraphQL
operation matching currently requires raw route payload fields such as vars,
so use a7 route create/update -f for those operation-specific routes.
version: "1"
services:
- id: graphql-engine-service
name: graphql-engine-service
upstream:
type: roundrobin
nodes:
- host: graphql-engine
port: 8080
weight: 1
routes:
- id: rest-bridge-user
name: rest-bridge-user
paths:
- /api/users/:id
methods:
- GET
service_id: graphql-engine-service
plugins:
degraphql:
query: "query getUser($id: ID!) { user(id: $id) { name email profile { bio } } }"
variables:
- id
Apply it to one gateway group:
a7 config sync -g prod-group -f graphql-rest-bridge.yaml
Verification
a7 service get gql-read-service -g prod-group -o json
a7 route get gql-queries -g prod-group -o json
a7 route get gql-mutations -g prod-group -o json
Traffic verification requires a deployed gateway and GraphQL backend:
curl -X POST https://gateway.prod.example.com/graphql \
-H "Content-Type: application/json" \
-H "apikey: frontend-secret" \
-d '{"query": "query getUser { user(id: 1) { name } }"}'
curl -X POST https://gateway.prod.example.com/graphql \
-H "Content-Type: application/json" \
-H "apikey: frontend-secret" \
-d '{"query": "mutation deleteUser { deleteUser(id: 1) { success } }"}'
Important Considerations
- Body parsing is required for GraphQL variables to be available.
- Batched requests may only expose variables for the first operation.
- Use route priorities when multiple routes share
/graphql. - Keep auth credentials under
a7 credential, not embedded directly in the consumer.
This page is generated from a7-recipe-graphql-proxy/SKILL.md in the api7/a7 repository. Browse all skills on the AI Agent Skills page.