Implementing Request/Response Transformation on API Gateway
Gateway-level transformation allows adapting data format between client and service without modifying service code. Typical tasks: field renaming, sensitive data filtering, adding system headers, versioning without API rewrite.
Transformation in Kong (Request/Response Transformer)
# Request transformation
curl -X POST http://localhost:8001/services/users-api/plugins \
-d "name=request-transformer" \
-d "config.add.headers[]=X-Service-Version:1.2.3" \
-d "config.add.headers[]=X-Request-ID:$(uuidgen)" \
-d "config.remove.headers[]=X-Real-IP" \
-d "config.rename.headers[]=Authorization:X-Auth-Token" \
-d "config.add.querystring[]=format:json"
# Response transformation
curl -X POST http://localhost:8001/services/users-api/plugins \
-d "name=response-transformer" \
-d "config.remove.headers[]=X-Internal-Server" \
-d "config.remove.headers[]=X-Powered-By" \
-d "config.remove.headers[]=Server" \
-d "config.add.headers[]=Cache-Control:no-store" \
-d "config.add.headers[]=X-Content-Type-Options:nosniff"
Request body transformation (JSON):
curl -X POST http://localhost:8001/services/users-api/plugins \
-d "name=request-transformer-advanced" \
-d 'config.add.body[]=source:web' \
-d 'config.remove.body[]=internal_debug_flag' \
-d 'config.rename.body[]=user_id:userId' # camelCase for legacy service
Transformation in APISIX (proxy-rewrite + response-rewrite)
{
"plugins": {
"proxy-rewrite": {
"uri": "/v2/users",
"method": "POST",
"headers": {
"set": {
"X-Tenant-ID": "$http_x_tenant_id",
"X-Service-Key": "internal-secret"
},
"remove": ["X-Forward-For", "X-Real-IP"]
}
},
"response-rewrite": {
"status_code": 200,
"headers": {
"set": {
"Access-Control-Allow-Origin": "https://app.company.com"
},
"remove": ["X-Powered-By"]
},
"body_base64": false,
"filters": [
{
"regex": "password",
"scope": "once",
"action": "remove"
}
]
}
}
}
Transformation in AWS API Gateway (Velocity Templates)
## Incoming request mapping
#set($inputRoot = $input.path('$'))
{
"userId": "$context.authorizer.user_id",
"tenantId": "$context.authorizer.tenant_id",
"data": {
"email": "$inputRoot.email",
"name": "$inputRoot.name"
},
"metadata": {
"ip": "$context.identity.sourceIp",
"userAgent": "$context.identity.userAgent",
"requestId": "$context.requestId"
}
}
Response transformation (flatten nesting):
## Integration Response mapping template
#set($inputRoot = $input.path('$.data'))
{
"id": "$inputRoot.id",
"email": "$inputRoot.email",
"name": "$inputRoot.name",
"createdAt": "$inputRoot.created_at"
}
Filtering Sensitive Fields in Response
-- Kong plugin: remove fields before sending to client
local function filter_response(body_json)
local sensitive = {"password_hash", "secret_key", "internal_id", "admin_notes"}
for _, field in ipairs(sensitive) do
body_json[field] = nil
end
return body_json
end
-- In plugin header_filter/body_filter
local body = cjson.decode(ngx.arg[1])
ngx.arg[1] = cjson.encode(filter_response(body))
Versioning via Transformation
Old client (v1 API) → Gateway adapts to v2 service format:
// KrakenD serverless middleware
var adaptV1toV2 = new TykJS.TykMiddleware.NewMiddleware({})
adaptV1toV2.NewProcessRequest(function(request, session) {
var body = JSON.parse(request.Body)
// Client sends v1 format: { "user_id": 123 }
// Service expects v2 format: { "userId": "123", "version": 2 }
var transformed = {
userId: String(body.user_id),
version: 2
}
request.Body = JSON.stringify(transformed)
return TykJS.TykMiddleware.ReturnData(request, {})
})
Protocol Transformation: REST → GraphQL
KrakenD supports GraphQL backend from REST client request:
{
"endpoint": "/api/v1/user/{id}",
"backend": [
{
"url_pattern": "/graphql",
"host": ["http://graphql-service:4000"],
"extra_config": {
"backend/graphql": {
"type": "query",
"query": "query GetUser($id: ID!) { user(id: $id) { id name email } }",
"variables": {
"id": "{id}"
}
}
}
}
]
}
XML ↔ JSON Conversion
# APISIX: convert XML response to JSON
plugins:
xml-json:
trim_space: true
cdata_to_json: false
omit_xml_declaration: false
Timeline
Setting up request/response transformation (headers, body, field filtering) — 1–2 business days.







