Backend Aggregation
Call two upstream services from a single workflow and return a merged response to the client. The client makes one request; the gateway calls both backends, combines the results, and returns a unified JSON object.
Typical scenario: A mobile app needs a "user profile" screen populated with data from two separate services — a user service (name, email, plan) and an activity service (last login, usage stats). Instead of two client-side calls, the gateway aggregates them into one response.
Prerequisites
- You are signed in to the Management UI as an admin or editor
- Two upstream HTTP services are accessible from the gateway network:
- User service:
https://user-service.internal/users/{userId} - Activity service:
https://activity-service.internal/activity/{userId}
- User service:
- A collection (e.g.
platform-api) and proxy (e.g.GET /profile/{userId}) are already created
Workflow overview
The workflow runs two HTTP calls in parallel, merges their outputs, builds the final JSON, and returns it.
Step 1 — Open the workflow canvas
- Open Collections, select
platform-api. - Open the
GET /profile/{userId}proxy. - Click the Workflow tab to open the canvas.
Step 2 — Add the trigger node
The http_trigger node is automatically present and represents the incoming request. It exposes the request path, headers, and query parameters to downstream nodes.
Confirm the trigger is configured with:
- Path parameter:
userId(matches the proxy path{userId})
Step 3 — Add parallel HTTP request nodes
You will call both services in parallel using a parallel_node feeding into two http_request_node instances.
- Drag a Parallel node from the node palette and connect it from
http_trigger. - In the parallel node settings, set Branches: 2.
Branch 1 — User service:
- Add an
http_request_nodeon the first parallel branch. - Configure:
- Method:
GET - URL:
https://user-service.internal/users/{{trigger.params.userId}} - Headers:
Content-Type: application/json
- Method:
- Name this node
fetch-user.
Branch 2 — Activity service:
- Add an
http_request_nodeon the second parallel branch. - Configure:
- Method:
GET - URL:
https://activity-service.internal/activity/{{trigger.params.userId}} - Headers:
Content-Type: application/json
- Method:
- Name this node
fetch-activity.
Use {{trigger.params.userId}} to pass the path parameter from the incoming request into each upstream URL.
Step 4 — Merge the responses
- Drag a Merge node and connect both
fetch-userandfetch-activityas inputs. - The merge node waits for both branches to complete before continuing.
- Name this node
merge-results.
After the merge node, both upstream responses are available as:
{{fetch-user.response.body}}{{fetch-activity.response.body}}
Step 5 — Build the unified response with a Set node
Use a set_node to assemble the merged JSON object you want to return.
- Drag a Set node and connect it from
merge-results. - Configure the output fields:
{
"userId": "{{trigger.params.userId}}",
"name": "{{fetch-user.response.body.name}}",
"email": "{{fetch-user.response.body.email}}",
"plan": "{{fetch-user.response.body.plan}}",
"lastLogin": "{{fetch-activity.response.body.lastLogin}}",
"apiCallsThisMonth": "{{fetch-activity.response.body.usage.calls}}"
}
- Name this node
build-profile.
Step 6 — Return the response
- Drag a Response node and connect it from
build-profile. - Configure:
- Status:
200 - Body:
{{build-profile.output}} - Content-Type:
application/json
- Status:
Step 7 — Save and publish
- Click Save on the workflow canvas.
- Return to the proxy settings and click Publish to make the endpoint live.
Verify
Test the aggregated endpoint:
curl -i https://gateway.example.com/platform-api/profile/user-42 \
-H "X-Client-ID: <client-id>" \
-H "Authorization: Bearer <token>" \
-H "X-Profile-ID: <profile-id>"
Expected response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"userId": "user-42",
"name": "Alice Nguyen",
"email": "[email protected]",
"plan": "pro",
"lastLogin": "2026-03-09T14:23:00Z",
"apiCallsThisMonth": 1842
}
Edge case — one backend fails:
If either upstream returns a non-2xx status, the workflow raises an error. To handle partial failures gracefully, add a condition_node after each http_request_node that branches on status code, returning a default value when the service is unavailable. See the Retry and Fallback recipe for the full pattern.
Observability
Open Logs → Request Logs and select the request to see:
- Which workflow steps ran
- Upstream response times for both services
- The final response body returned to the client
Next steps
- Retry and Fallback — handle partial backend failures
- Workflow Nodes — Parallel — parallel branch reference
- Workflow Nodes — Merge — merge node reference
- Workflow Expressions — using
{{}}template expressions