This article is a part of the series "Hands-On With Apache APISIX Ingress."

Other articles in the series:

With APISIX’s request batching capability, you can group multiple independent API requests from the client into a single request. APISIX then turns this request into separate requests to your upstream services, aggregates the response, and sends it back to the client as a single response.

Batching can be useful in many practical scenarios where multiple independent requests are needed to fulfill a task. For example, the Conference API exposes two different endpoints, /speaker/{speakerId}/sessions and /speaker/{speakerId}/topics, for listing the sessions and topics belonging to a speaker, respectively. You can make two API calls to show both in a front-end application.

Making two requests
Making two requests

Sometimes this might not be ideal.

This might not always be ideal. Instead, you can use APISIX’s request batching capability to group these requests into one, reducing the number of client-server calls.

Batching requests
Batching requests

Batching requests reduces the number of client-server calls.

In this article, we will look at configuring this through APISIX Ingress in Kubernetes. If you are using the Admin API instead, you can check this article.

Set Up the Example

First, let’s set up the Conference API as our upstream. Like we did in the previous article, we will configure this external service in our upstream (ApisixUpstream) as shown below:

apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
  name: conference-api-upstream
spec:
  externalNodes:
    - type: Domain
      name: conferenceapi.azurewebsites.net

Enable Batching

Request batching in APISIX is achieved through the batch-requests plugin. You can enable the plugin by adding it to your Helm values file, as shown below:

plugins:
  - batch-requests
  - public-api

You can also enable the public-api plugin to expose the route to external requests.

Note: You should add all plugins being used to this list, as it will replace the plugins enabled by default.

Configure Routes

We will create a route with the path /speaker that returns both the sessions and topics of a particular speaker using the public-api plugin. You can do this using the ApisixRoute CRD:

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: speaker-route
spec:
  http:
    - name: speaker
      match:
        paths:
          - /speaker
      plugins:
        - name: public-api
          enable: true
          config:
            uri: /apisix/batch-requests
      backends:
        - serviceName: apisix-admin
          servicePort: 9180

Note: The ApisixRoute CRD always requires you to configure an upstream to a route. To work around this, you can use the Admin API as the upstream, as shown above, in scenarios where you don’t have/need an upstream.

The above route extracts the individual requests needed to be made by APISIX. To fulfill these requests using the upstream, we have to create another two routes:

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: speaker-topics-route
spec:
  http:
    - name: speaker-topics
      match:
        hosts:
          - conferenceapi.azurewebsites.net
        paths:
          - /*
        exprs:
          - subject:
              scope: Path
              name: topics
            op: RegexMatch
            value: "^/speaker/[^/]*/topics$"
        methods:
          - "GET"
      upstreams:
        - name: conference-api-upstream

---

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: speaker-sessions-route
spec:
  http:
    - name: speaker-sessions
      match:
        hosts:
          - conferenceapi.azurewebsites.net
        paths:
          - /*
        exprs:
          - subject:
              scope: Path
              name: sessions
            op: RegexMatch
            value: "^/speaker/[^/]*/sessions$"
        methods:
          - "GET"
      upstreams:
        - name: conference-api-upstream

This may seem complicated, so here is the explanation. We are matching requests using a regular expression in the path. So regardless of the value of {speakerId} in /speaker/{speakerId}/sessions and /speaker/{speakerId}/topics, we will match the request and forward it to the upstream.

Note: If you are configuring APISIX through the Admin API, this is much simpler and reduces to "uris": ["/speaker/*/topics","/speaker/*/sessions"]. However, we must use regular expressions since the ApisixRoute CRD does not support URI patterns with * in the middle.

Test the Routes

To test the route, we can send a request to the exposed /speaker endpoint:

curl -i http://127.0.0.1:50404/speaker -X POST -d \
'{
  "pipeline": [
    {
      "method": "GET",
      "path": "/speaker/1/topics"
    },
    {
      "method": "GET",
      "path": "/speaker/1/sessions"
    }
  ]
}'

APISIX will parse this request body and make separate requests to each endpoint. This saves a lot of network traffic as APISIX is closer to the upstream than the client having to make these calls itself.

The aggregated response from APISIX will be as shown below:

[
  {
    "body": "{\r\n  \"collection\": {\r\n    \"version\": \"1.0\",\r\n    \"links\": [],\r\n    \"items\": [\r\n      {\r\n        \"href\": \"https://conferenceapi.azurewebsites.net/topic/8\",\r\n        \"data\": [\r\n          {\r\n            \"name\": \"Title\",\r\n            \"value\": \"Microsoft\"\r\n          }\r\n        ],\r\n        \"links\": [\r\n          {\r\n            \"rel\": \"http://tavis.net/rels/sessions\",\r\n            \"href\": \"https://conferenceapi.azurewebsites.net/topic/8/sessions\"\r\n          }\r\n        ]\r\n      },\r\n      {\r\n        \"href\": \"https://conferenceapi.azurewebsites.net/topic/10\",\r\n        \"data\": [\r\n          {\r\n            \"name\": \"Title\",\r\n            \"value\": \"Mobile\"\r\n          }\r\n        ],\r\n        \"links\": [\r\n          {\r\n            \"rel\": \"http://tavis.net/rels/sessions\",\r\n            \"href\": \"https://conferenceapi.azurewebsites.net/topic/10/sessions\"\r\n          }\r\n        ]\r\n      }\r\n    ],\r\n    \"queries\": [],\r\n    \"template\": {\r\n      \"data\": []\r\n    }\r\n  }\r\n}",
    "status": 200,
    "headers": {
      "Expires": "-1",
      "Connection": "keep-alive",
      "Pragma": "no-cache",
      "Content-Length": "953",
      "Server": "APISIX/3.2.0",
      "Content-Type": "application/vnd.collection+json",
      "X-AspNet-Version": "4.0.30319",
      "Cache-Control": "no-cache",
      "X-Powered-By": "ASP.NET"
    },
    "reason": "OK"
  },
  {
    "body": "{\r\n  \"collection\": {\r\n    \"version\": \"1.0\",\r\n    \"links\": [],\r\n    \"items\": [\r\n      {\r\n        \"href\": \"https://conferenceapi.azurewebsites.net/session/206\",\r\n        \"data\": [\r\n          {\r\n            \"name\": \"Title\",\r\n            \"value\": \"\\r\\n\\t\\t\\tjQuery Mobile and ASP.NET MVC\\r\\n\\t\\t\"\r\n          },\r\n          {\r\n            \"name\": \"Timeslot\",\r\n            \"value\": \"05 December 2013 09:00 - 10:00\"\r\n          },\r\n          {\r\n            \"name\": \"Speaker\",\r\n            \"value\": \"Scott Allen\"\r\n          }\r\n        ],\r\n        \"links\": [\r\n          {\r\n            \"rel\": \"http://tavis.net/rels/speaker\",\r\n            \"href\": \"https://conferenceapi.azurewebsites.net/speaker/16\"\r\n          },\r\n          {\r\n            \"rel\": \"http://tavis.net/rels/topics\",\r\n            \"href\": \"https://conferenceapi.azurewebsites.net/session/206/topics\"\r\n          }\r\n        ]\r\n      }\r\n    ],\r\n    \"queries\": [],\r\n    \"template\": {\r\n      \"data\": []\r\n    }\r\n  }\r\n}",
    "status": 200,
    "headers": {
      "Expires": "-1",
      "Connection": "keep-alive",
      "Pragma": "no-cache",
      "Content-Length": "961",
      "Server": "APISIX/3.2.0",
      "Content-Type": "application/vnd.collection+json",
      "X-AspNet-Version": "4.0.30319",
      "Cache-Control": "no-cache",
      "X-Powered-By": "ASP.NET"
    },
    "reason": "OK"
  }
]

What’s Next?

You can learn more about using plugins or creating custom ones and configuring them through APISIX from other articles in this series.

This article was written after a question by an APISIX Slack user. If you want to see articles on specific topics, you can reach out to me at navendu@apache.org

See the complete list of articles in the series “Hands-On With Apache APISIX Ingress”.