Using Subrequests with the NGINX JavaScript Module to Batch API Requests

Original: https://www.nginx.com/blog/batching-api-requests-nginx-plus-javascript-module/

The version of the NGINX JavaScript module (formerly called nginScript) released with NGINX Plus R15 can now issue subrequests, meaning that requests can be initiated in JavaScript code, allowing a whole new set of use cases to be addressed.

One of these use cases is batching API requests so that a single API request from a client can be turned into multiple API requests to a set of backend servers, and the responses aggregated into a single response to the client.

This post builds on the subrequest example in the NGINX Plus R15 announcement, which shows how to use subrequests to send the same request to two backend servers, and return only the first response to the client.

Introduction

The goal of this post is to provide working examples of API request batching that are simple and straightforward. The examples meet all of the following requirements:

Solution Overview

In addition to using the new subrequest feature of NGINX JavaScript, this solution utilizes NGINX Plus’ key‑value store feature, allowing configuration changes to be made dynamically with the NGINX Plus API.

The following graphics illustrate the two supported request styles.

Final‑element request style:

Client makes final-element style requests to an API

Query‑string request style:

Client makes query-string style requests to an API

In both examples, the client needs to get information about a product from the catalog, inventory, and review services. It sends one request to NGINX Plus, either passing the item number as part of the URI or in the query string. Requests are then sent to the three services and the responses are aggregated into a single response to the client.

There are two components required to get this working with NGINX Plus: a JavaScript program and the NGINX Plus configuration.

The JavaScript Program

The JavaScript function batchAPI() is called for each client request. It gets the comma‑separated list of API URIs from the key‑value store and iterates through them, making a subrequest for each, and specifying the callback function done() to process each response. For final‑element style requests, the last element of the URI, stored in the NGINX variable $uri_suffix, is passed as the query string of each subrequest, along with any other query‑string arguments in the original client URI. For query‑string style requests, the query string from the client requests is passed as the query string of each subrequest.

The calls are made in the order they are listed in the key‑value store, but are asynchronous, so the responses can come back in any order. The responses are aggregated into one response to the client in the order in which they are received. Here is a minimal version of the JavaScript program:

A version of the JavaScript program with more extensive error handling, logging, and comments is also available in the GitHub Gist repo as batch-api.js.

Minimal NGINX Plus Configuration

The following NGINX Plus configuration implements the two request styles discussed above, using a group of upstream servers. To keeps things simple, this configuration assumes that the upstream servers are able to translate a call of the form /myapi/service/item# (final‑element style) to /myapi/service.php/?item=item# (query‑string style), which is the “native” URI format for PHP, the language for our sample catalog, inventory, and review services.

For a more extensive NGINX Plus configuration that performs the translation itself, see Expanding the NGINX Configuration to Translate URIs below.

Let’s look at the configuration file section by section and explain what each part does:

Here’s the complete configuration:

Configuring the Batched API Requests

We’re using the NGINX Plus key‑value store feature to map inbound client requests to a set of API requests to execute. The configuration in the previous section defines separate key‑value stores for the two request styles:

In both key‑value stores, the value associated with each key is a comma‑separated list of URIs that are made available at runtime in the $batch_api or $batch_api2 variables.

For final‑element style requests, we want to map a client request for /batch-api/product to requests to the three services /myapi/catalog, /myapi/inventory, and /myapi/review, such that a request for /batch-api/product/14286 results in requests to /myapi/catalog/14286, /myapi/product/14286 and /myapi/review/14286.

To add these mappings to the batch_api key‑value store, we send the following request to the NGINX Plus instance running on the local machine:

curl -id '{"/batch-api/product":"/myapi/catalog,/myapi/inventory,/myapi/review"}' http://localhost:/api/3/http/keyvals/batch_api

For the query‑string style requests, we want to map a client request for /batch-api2/product to requests to the three services /myapi/catalog.php, /myapi/inventory.php, and /myapi/review.php, such that a request for /batch-api2/product?item=14286 results in requests to /myapi/catalog?item=14286, /myapi/product?item=14286 and /myapi/review?item=14286.

To add these mappings to the batch_api2 key‑value store, we send this request:

curl -id '{"/batch-api2/product":"/myapi/catalog.php,/myapi/inventory.php,/myapi/review.php"}' http://localhost:/api/3/http/keyvals/batch_api2

Running the Examples

The same PHP pages handle requests made in both request styles. For simplicity, we assume the backend servers are able to translate final‑element style URIs (/myapi/service/item#) to query‑string style URIs (/myapi/service.php?item=item#). It’s not necessary to translate query‑string style URIs, as that is the “native” URI format for PHP programs.

All of the services return a JSON‑formatted response that includes the service name and item argument. For example, a request for /myapi/catalog.php?item=14286 results in the following response from catalog.php:

{"service":"Catalog","item":"14286"}

Although the PHP programs used in these examples include the service name in the response, this might not be the case for all services. To help match responses to the services that generated them, the JavaScript program includes the URI in the aggregated response.

For example, the aggregated response for a request for /batch-api/product/14286 is as follows (although the order of the three component responses might vary):

[["/myapi/review/14286",{"service":"Review","item":"14286"}],["/myapi/inventory/14286",{"service":"Inventory","item":"14286"}],["/myapi/catalog/14286",{"service":"Catalog","item":"14286"}]]

Expanding the NGINX Plus Configuration to Translate URIs

As mentioned, the minimal NGINX Plus configuration discussed above assumes that the API servers are able to translate URIs of the format /myapi/service/item# to /myapi/service.php/?item=item#. We can also implement the translation in the NGINX Plus configuration by making the following changes.

Here’s the complete configuration:

Additional Components

The sample JavaScript programs and NGINX Plus configurations can be adapted to other styles of APIs, but if you want to test using all the components used to create this blog post, they are available in our Gist repo on GitHub, including the NGINX Unit configuration for serving the PHP pages:

batch-api.js
catalog.php
inventory.php
review.php
unit.config

Conclusion

These examples show how to batch API requests and provide an aggregated response to the client. The NGINX Plus key‑value store allows us to configure the API requests dynamically with the NGINX Plus API. API systems vary quite widely in their exact operations so there are many enhancements that can be made to this example to fit the needs of a particular API.

You can start testing NGINX JavaScript subrequests with NGINX Open Source, but if you want to try the API batching examples or test other use cases that take advantage of the NGINX Plus features like the key‑value store and NGINX Plus API, request a 30-day free trial and start experimenting.

Retrieved by Nick Shadrin from nginx.com website.