Batch Request

Batch Request is lightweight connect/express middleware for Node.js which allows clients to send multiple requests to a server running Node.js in batch.

It is essentially a wrapper around mikeal's request library and adds the ability for a client to send a single request that represents many, have them all run, then return a single response.

Batch Request is open source software and is released under the developer and business-friendly MIT license.

Proudly written in Washington, D.C. by:

Or grab the latest version on Github.


Usage

applications

So when to use Batch Request?

Below are a few good examples:

Optimizing APIs for Mobile

We developed Batch Requests at SocialRadar particularly for use with mobile devices connecting to our APIs written in Node.js.

For mobile devices particularly, spinning the cell modem up is an expensive operation and includes a fair amount of latency (we found an average of around 300ms from an iPhone 5s on LTE!).

We wanted to avoid that latency, but also spinning up the cell modem over and over to execute a chain of requests resulted in unnecessary battery drainage!

So we were taking a double hit anytime we sent multiple requests, not only was it slower but it was also but killing the battery faster.

We wrote this library to have a way to send API requests in batch from these mobile devices to our API to minimize these negative effects.

Maintaining RESTful purity

Further, there are situations where API developers are tempted to stray from RESTful principles in order to minimize requests. With Batch Request such compromises are unnecessary.

For example, let’s say you have an application with an API and you have 3 different underlying model objects: Users, Devices, Sessions. Users have Devices and Users have Sessions, each of which is linked to a specific Device.

It’s tempting to write a non-RESTful, more SOA endpoint such as /updateDevicesAndSessions in order to make all of these changes in one shot rather than operating on the resources one after the next in three pure RESTful calls to update each object.

However, with Batch Request you can maintain the benefits of a cheat like this (only one request to update multiple things) while maintainting REST purity.

middleware

It is primarily built to be used in Node.js as middleware with Connect or Express.

npm install batch-request

then in your app

// Load Batch Request
  var batch = require('batch-request')();

  // Use Batch Request as middleware on an endpoint you want to service batch requests
  app.post('/batch', batch);

and that’s it! Any POSTs to the '/batch' endpoint will be handled by Batch Request.

validation

We also recommend enabling the bundled validation middleware we include which will ensure the request sent to be batched has the right format. To do so, just update the routing line as follows:

// Adding the Batch Request validation middleware
  app.post('/batch', batch.validate, batch);

And that’s it!

Our validation middleware will run and ensure any request is valid before passing it on to Batch Request. This validation middleware will even respond directly (unless that option is disabled of course!)

Validation happens by some sanity checks and the options specified below.

Of course you can always ignore this and use your own validation middleware as well, but be careful to prevent your server from being used as a proxy.

options

In order to customize, rather than just immediately invoking the function exported by Batch Request with no arguments, it’s possible to pass in a JavaScript object representing any options you’d like to add.

// Get the default options
  var batch = require('batch-request')();

  // Specify that you only want max simultaneous requests at 5
  var batch = require('batch-request')({
      max: 5
  });

Then when you use Batch Request, it will use these options.

Available options:

Option Notes Default
max This is the maximum number of requests Batch Request will accept in one batch. Any more than this max will result in a 400 error. 20
validateRespond Whether or not to respond in the validation middleware. Setting to false leaves it up to you to respond. true
allowedHosts Array list of strings which represent allowed hosts. For example ['socialradar.com', 'localhost:3000']. Must include port if used. If any request is not in the list of allowedHosts, the Batch Request validator will return a 400 error. null

Requests

intro

The simplest case is just to send a single request as JSON. A request object is simply a JSON object with one or more keys.

Each key represents one request. The key can be any string value of your choosing. This key is used to identify your request and will be sent back in your reply.

So let’s get started. First, start with a blank JSON object:

{ 
}

Now let’s add one super basic request:

{
    "myRequest": {
        "method": "GET",
        "uri": "http://api.mysite.com/users/1/name"
    }
}

When sent to the server to an endpoint using the Batch Request middleware, the server will perform a GET request to a local endpoint called /users/1/name and will return the result that looks like the following:

{
    "myRequest": {
        "statusCode": 200,
        "body": "Victor",
        "headers": {
            "x-powered-by": "Express",
            "content-type": "text/html",
            "date": "Wed, 06 Nov 2013 21:33:18 GMT",
            "connection": "close",
            "transfer-encoding": "chunked"
        },
    }
}

But that’s not too exciting, why would we use a batching library for a single request?

This library was built for batching. So let’s dive in!

batch

To add more than one request, just add more keys to the root level JSON object, one per additional request:

{
    "myRequest1": ...,
    "myRequest2": ...,
    "myRequest3": ...
}

This is 3 requests in one blob of JSON (with the request body replaced with ... of course). The server will execute each request and send back a JSON object with the same keys and the results of each request as the value for each key.

Let’s see an example more filled out. The JSON request we will POST to our endpoint using the Batch Request middleware:

{
    "myRequest1": {
        "method": "GET",
        "uri": "http://api.mysite.com/users/1/first_name"
    },
    "myRequest2": {
        "method": "GET",
        "uri": "http://api.mysite.com/users/1/email"
    },
    "myRequest3": {
        "method": "GET",
        "uri": "http://api.mysite.com/users/1/company"
    },
}

The response we receive:

{
    "myRequest1": {
        "statusCode": 200,
        "body": "Victor",
        "headers": {...}
    },
    "myRequest2": {
        "statusCode": 200,
        "body": "victor@socialradar.com",
        "headers": {...}
    },
    "myRequest3": {
        "statusCode": 200,
        "body": "SocialRadar",
        "headers": {...}
    },
}

Note, I’ve omitted the body of the headers as it’s rather boring and not too helpful. But they’d all look something like this:

{
    "x-powered-by": "Express",
    "content-type": "application/json; charset=utf-8",
    "content-length": "81",
    "date": "Mon, 16 Dec 2013 15:30:24 GMT",
    "connection": "keep-alive" 
}

I’ll continue doing so throughout this documentation.

This assumes of course we have these three endpoints on our server, one each for name, email, and company on a user which each return simply a string. I question my own API design as rather silly!

post

In order to do a POST (or PUT or PATCH) you’ll need to send along the data.

This is as simple as adding a key of body whose value is the data you’d like to POST.

For example, to perform a POST followed by a GET in one request, use the following sample JSON:

{
    "myRequest1": {
        "method": "POST",
        "uri": "http://api.mysite.com/users/1",
        "body": {
            "first_name": "Victor"
        }
    },
    "myRequest2": {
        "method": "GET",
        "dependency": "myRequest1",
        "uri": "http://api.mysite.com/users/1"
    }
}

And the resulting response:

{
    "myRequest1": {
        "statusCode": "204",
        "body": null,
        "headers": {...}
    },
    "myRequest2": {
        "statusCode": "200",
        "body": {
            "first_name": "Victor",
            "email": "victor@socialradar.com",
            "company": "SocialRadar"
        },
        "headers": {...}
    }
}

faq

Here compiled are a series of the frequently asked questions about requests

Q: Does the key I pick matter?
A: No! Pick any text string you want. It in no way effects how your request is handled. The one caveat is that you cannot send 2 requests with the same key, but otherwise the name will not affect how the request is handled.

Q: Does Batch Request support arbitrary nesting of requests?
A: No. It expects a flat object. To model dependencies and other tiered requests, see our section on dependency modeling.

Request Options

auth

Send basic auth information with this request. Expects an object with user and pass keys.

{
    "myRequest1": {
        "auth": {
            "user": "victor",
            "pass": "mysecurepassword"
        },
        "uri": "http://api.mysite.com/users/1"
    }
}

default: none

followRedirect

This is an option which, if true will follow HTTP 3XX responses as redirects.

To disallow following redirects, set to false

{
    "myRequest1": {
        "followRedirect": false,
        "uri": "http://api.mysite.com/users/1"
    }
}

default: true

headers

To specify headers to send with a particular request, simply add a key of headers and an object with any headers to send along.

{
    "myRequest1": {
        "headers": {
            "Access-Token": "FwSyxLkAyImqpIGDMRbZ0hc6ZzrMC7cWY4BwJOE4lN1UnKiMlQ",
            "CustomHeader": "Banana"
        },
        "uri": "http://api.mysite.com/users/1"
    }
}

default: none

json

Boolean, will specify that the included body is JSON and adds a header of Content-type: application/json. This is mostly a convenience method.

{
    "myRequest1": {
        "json": true,
        "method": "POST",
        "uri": "http://api.mysite.com/users/1",
        "body": {
            "first_name": "Victor"
        }
    }
}

method

Shown above, but repeated here for the sake of completeness.

To send a request using a particular method (GET, DELETE, PATCH, POST, PUT) specify it with a key of method. For example:

{
    "myRequest1": {
        "method": "DELETE",
        "uri": "http://api.mysite.com/users/1"
    }
}

This is not required as it defaults to GET so the following is a perfectly legitimate request:

{
    "myRequest1": {
        "uri": "http://api.mysite.com/users/1"
    }
}

which is equivalent to:

{
    "myRequest1": {
        "method": "GET",
        "uri": "http://api.mysite.com/users/1"
    }
}

default: GET

body

In order to do a POST (or PUT or PATCH) you’ll need to send along the data.

This is as simple as adding a key of body whose value is the data you’d like to POST.

For example, to perform a POST followed by a GET in one request, use the following sample JSON:

{
    "myRequest1": {
        "method": "POST",
        "uri": "http://api.mysite.com/users/1",
        "body": {
            "first_name": "Victor"
        },
        "json": true
    },
    "myRequest2": {
        "method": "GET",
        "dependency": "myRequest1",
        "uri": "http://api.mysite.com/users/1"
    }
}

And the resulting response:

{
    "myRequest1": {
        "statusCode": "204",
        "body": null,
        "headers": {...}
    },
    "myRequest2": {
        "statusCode": "200",
        "body": {
            "first_name": "Victor",
            "email": "victor@socialradar.com",
            "company": "SocialRadar"
        }
        "headers": {...}
    }
}

default: none

strictSSL

This is an option which, if true will require that SSL certs be valid. If you generate your own cert, having this set to true will likely cause the request to fail.

{
    "myRequest1": {
        "method": "GET",
        "strictSSL": false,
        "uri": "https://api.mysite.com/users/1"
    }
}

default: true

timeout

To specify a timeout, add a key of timeout with the request with a value which is the number of milliseconds to wait before considering a request timed out.

{
    "myRequest1": {
        "uri": "http://api.mysite.com/users/1",
        "timeout": 3000
    }
}

default: 120000 (2 minutes)

others

Internally we are using the node library, request, so see the following for an exhaustive list of options:

Request library options

Dependencies

default ordering

So by now you may be thinking, batching is awesome and useful, but in what order are those requests performed?

Let’s take the basic request from above:

{
    "myRequest1": {
        "method": "GET",
        "uri": "http://api.mysite.com/users/1/first_name"
    },
    "myRequest2": {
        "method": "GET",
        "uri": "http://api.mysite.com/users/1/email"
    },
    "myRequest3": {
        "method": "GET",
        "uri": "http://api.mysite.com/users/1/company"
    },
}

These 3 requests will all be run in parallel. We do this as it’ll be the fastest for most use cases.

But what if you want these to run in series? What if you want myRequest1 and myRequest3 to run in parallel but myRequest2 needs to wait for myRequest1 to complete?

For example, let’s say you want to create a User and create another item that depends on the existence of that user?

With the above simple pattern, nothing can be done, you’ll have to do perform each of these requests in a separate API call as they all run in parallel.

We have the technology. We can make it better. And we have.

Enter, dependencies.

overview

We came up with a very simple and straightforward way of modeling almost any dependency imaginable on the client.

Essentially, for any series of requests, all the client needs to do in order to enforce an order is specify a dependency and Batch Request will handle all of the rest.

In more concrete terms, on any request you specify a key dependency and a value which is the string name of the request on which it depends.

For example, to specify that a User object must be created before a Device object is added for that user, I would specify dependency: createUser on the device.

This is best illustrated by example, so let’s dive in.

singular

For this one of the request objects does not have any dependencies and the second is dependent on that one.

{
    "myRequest1": {
        "uri": "http://api.mysite.com/users/1/first_name"
    },
    "myRequest2": {
        "dependency": "myRequest1",
        "uri": "http://api.mysite.com/users/1/email"
    }
}

As shown above, all that’s necessary to ensure myRequest2 runs after myRequest1 is to provide the dependency key with a value which is the key of the request object on which it depends.

series

In order to ensure items run in series, simply flag each as a dependency of the previous:

{
    "myRequest1": {
        "uri": "http://api.mysite.com/users/1/first_name"
    },
    "myRequest2": {
        "dependency": "myRequest1",
        "uri": "http://api.mysite.com/users/1/email"
    },
    "myRequest3": {
        "dependency": "myRequest2",
        "uri": "http://api.mysite.com/users/1/company"
    }
}

Here since myRequest3 has a dependency on myRequest2 which has a dependency on myRequest1 they will run in series, one before the next.

complex

What follows below is a rather complex chaining. This is actually a real request used in the unit tests of this module.

{
    "time1": {
        "url": "http://api.mysite.com/users/1/hammertime"
    },
    "time2": {
        "dependency": "time1",
        "url": "http://api.mysite.com/users/1/delay"
    },
    "time3": {
        "dependency": "time2",
        "url": "http://api.mysite.com/users/1/hammertime"
    },
    "time4": {
        "dependency": "time1",
        "url": "http://api.mysite.com/users/1/delay"
    },
    "time5": {
        "dependency": "time4",
        "url": "http://api.mysite.com/users/1/delay"
    },
    "time6": {
        "dependency": "time4",
        "url": "http://api.mysite.com/users/1/delay"
    },
    "time7": {
        "dependency": "time4",
        "url": "http://api.mysite.com/users/1/delay"
    }
}

Batch Request will ensure that all of the dependencies are met and things are run in the proper order.

multiple

Unfortunately, Batch Request does not yet support multiple dependencies.

Support is planned, so you could say something like:

{
    "time1": {
        "url": "http://api.mysite.com/users/1/hammertime"
    },
    "time2": {
        "url": "http://api.mysite.com/users/1/delay"
    },
    "time3": {
        "dependency": ["time1", "time2"],
        "url": "http://api.mysite.com/users/1/hammertime"
    }
}

In such a case, time3 would be dependent on both time1 and time2.

Unfortunately this is not yet implemented. It gets a bit tricky as it’s rather easy to run into issues with circular dependencies, so we haven’t tackled it yet. We do have an app to launch ;)

Coming soon! (sooner if someone else takes a crack at it and submits a pull request!)


Todo


Change Log

0.0.3 08 Nov 2013diff

  • First full release!
  • All base functionality now works including batching, dependencies, and the validation middleware

Acknowledgements & Thanks

This documentation is based on the layout of the Zepto.js which itself is based on the layout of the Backbone.js documentation, which is released under the MIT license.

© 2013 SocialRadar
Batch Request and this documentation are released under the terms of the MIT license.