Search code examples
node.jsnode-request

Subclassing, extending or wrapping Node.js 'request' module to enhance response


I'm trying to extend request in order to hijack and enhance its response and other 'body' params. In the end, I want to add some convenience methods for my API:

var myRequest = require('./myRequest');
myRequest.get(function(err, hijackedResponse, rows) {
    console.log(hijackedResponse.metadata)
    console.log(rows)
    console.log(rows.first)
});

According to the Node docs on inherits, I thought I could make it work (and using the EventEmitter example in the docs works OK). I tried getting it to work using @Trott's suggestion but realized that for my use case it's probably not going to work:

// myRequest.js
var inherits = require('util').inherits;
var Request = require("request").Request;

function MyRequest(options) {
    Request.call(this, options);
}

inherits(MyRequest, Request);

MyRequest.prototype.pet = function() {
    console.log('purr')
}

module.exports = MyRequest;

I've been toying with extend as well, hoping that I could find a way to intercept request's onRequestResponse prototype method, but I'm drawing blanks:

var extend = require('extend'),
    request = require("request")

function myResponse() {}

extend(myResponse, request)

// maybe some magic happens here?

module.exports = myResponse

Ended up with:

var extend = require('extend'),
    Ok = require('objectkit').Ok

function MyResponse(response) {
    var rows = Ok(response.body).getIfExists('rows');
    extend(response, {
        metadata: extend({}, response.body),
        rows: rows
    });
    response.first = (function() {
        return rows[0]
    })();
    response.last = (function() {
        return rows[rows.length - 1] || rows[0]
    })();
    delete response.metadata.rows
    return response;
}

module.exports = MyResponse

Keep in mind in this example, I cheated and wrote it all inside the .get() method. In my final wrapper module, I'm actually taking method as a parameter.


Solution

  • UPDATED to answer the edited question:

    Here's a rough template for the contents of your myResponse.js. It only implements get(). But as a bare bones, this-is-how-this-sort-of-thing-can-be-done demo, I hope it gets you going.

    var request = require('request');
    
    var myRequest = {};
    
    myRequest.get = function (callback) {
        // hardcoding url for demo purposes only
        // could easily get it as a function argument, config option, whatever...
        request.get('http://www.google.com/', function (error, response, body) {
            var rows = [];
            // only checking error here but you might want to check the response code as well
            if (!error) {
                // mess with response here to add metadata. For example...
                response.metadata = 'I am awesome';
    
                // convert body to rows however you process that. I'm just hardcoding.
                // maybe you'll use JSON.parse() or something.
                rows = ['a', 'b', 'c'];
    
                // You can add properties to the array if you want.
                rows.first = 'I am first! a a a a';
            }
    
            // now fire the callback that the user sent you...
            callback(error, response, rows);
        });
    };
    
    module.exports = myRequest;
    

    ORIGINAL answer:

    Looking at the source code for the Request constructor, it requires an options object that in turn requires a uri property.

    So you need to specify such an object as the second parameter in your call():

    Request.call(this, {uri: 'http://localhost/'});
    

    You likely don't want to hard code uri like that inside the constructor. You probably want the code to look something more like this:

    function MyRequest(options) {
        Request.call(this, options);
    }
    
    ...
    
    var myRequest = new MyRequest({uri: 'http://localhost/'});
    

    For your code to work, you will also need to move util.inherits() above the declaration for MyRequest.prototype.pat(). It appears that util.inherits() clobbers any existing prototype methods of the first argument.