Routing with syntactic sugar


License
MIT
Install
npm install beanpoll@0.2.14

Documentation

build status

Beanpole - Routing framework

Motivation

  • Abstract communication between parts of an application
    • keeps code modular
    • works in-app, or with other protocols: amqp, http, etc.

This:

router.on({

    /**
     */

    'pull auth/user': function(req, res) {
        //auth here
    },

    /**
     */

    'pull auth/user -> add/photos': function(req, res) {
        //add photos here
    }
})

Versus somethine like this:

var addPhotos = function(req, res) {
    authUser(req, res, function() {
        //do stuff here
    });
}

Projects using Beanpole

  • celeri - CLI library
  • bonsai - application server
  • leche - Framework to build frontend / backend applications with the same code.
  • daisy - Expose beanpole to: http, websockets, amqp (rabbitmq), etc.
  • beandocs - Generate documentation from your beanpole route comments.
  • beanprep - Scans beans in a given directory, and installs their dependencies.
  • cupboard - Reverse package manager.

Beanpole ports

Overview

Alt ebnf diagram

The basic route consists of a few parts: the type of route, and the channel. Here are some examples:

router.on('pull hello/:name', ...);

and

router.on('push hello/:name', ...);           

Push Routes:

  • Used to broadcast a message, or change (1 to many).
  • Doesn't expect a response.
  • Multiple listeners per route.

Pull Routes:

  • Used to request data from a particular route (1 to 1).
  • Expects a response.
  • One listener per route.
  • examples:
    • request to http-exposed route

Collect Routes:

  • Used to request data from many listeners (1 to many, similar to pull).
  • Expects a response.

Error Handling

function auth(credits, callback) {

    if(credits.user != 'user' || credits.pass != 'pass') return callback(new Error('invalid credits'));

    callback(false, { user: 'user', pass: 'pass' });
}


router.on({

    'pull authenticate': function(req, res) {

        //don't bother handling errors - done by response
        auth(req.query, res.success(function(user) {

            res.end(user);

        }));
    }
})



//error
var req = router.request('authenticate').
error(function(err) {
    console.log(err.stack);
}).
success(function(response) {
    console.log(response);
}).
query({ user: 'user', pass: 'bad pass' }).
pull();

Custom Routes

You can easily create custom route handlers. Take celeri for example:

var beanpoll = require('beanpoll'),
structr = require('structr');

//handles the message, response, and middleware
var CmdMessenger = structr({

    _next: function(middleware) {

        var self = this;    

        try {

            //call the command handler, and wrap the LAST parameter as a next function
            middleware.listener(Structr.copy(middleware.params, data), function() {
                return self.next(); 
            });     

        } catch(e) {
            self.response.error(e)
        }

    }

}, beanpoll.Messenger);


//the "Event Emitter"
var CmdDirector = structr({

    _newMessenger: function(message, middleware) {
        return new CmdMessenger(message, middleware, this);
    }

}, beanpoll.Director);



var router = beanpoll.router();


//use the new plugin
router.use(function() {
    return {
        name: 'console',
        director: new CmdDirector('celeri', router)
    }
});

//use it:
router.on('console say/hello', function(data, next) {
    //do stuff here
});

Middleware can also be specified without using the token: ->.An example:

router.on({               

    /**
     */

    'pull my/*': function()
    {
        //authorize user
    },  

    /**
     */

    'pull my/profile': function()
    {                 
        //goes through authorization first 
    }
});

Providing a wildcard * tells the router that anything after the route must go through it.

Managing very long routes

You may run into a route which looks like this:

router.on({
    'pull -public -method=POST remove/cache/subscribers -> profile/validate/SAVE_ARTICLE -> groups/:group/subscribers OR groups/:group/subscribers/add': function() {

});

To fix the ugliness, breakup the route and escape any linebreaks:

router.on({
    'pull \
    -public -method=POST \
    remove/cache/subscribers -> \
        profile/validate/SAVE_ARTICLE -> \
            groups/:group/subscribers OR \
            groups/:group/subscribers/add': function() {

    }
})

You can also split it up:

router.on({
    'pull \
    remove/cache/subscribers -> \
    profile/validate/SAVE_ARTICLE -> \
        validate/group/subscribers': function() {

    }
})

router.on({
    'pull \
    -public -method=POST \
    validate/group/subscribers ->
        groups/:group/subscribers OR \
        groups/:group/subscribers/add': function() {

    }
})

Methods

router.on(type[,listener])

Listens to the given routes

  • type - string or object. String would contain the route. Object would contain multiple routes / listeners
  • listener - function listening to the route given.

router.request(router)

returns the request builder

router.request('signup/user').
query({ username: 'blarg' }).
headers({ 'Content-Type': 'application/json' }).

//called when the second param is present. 
success(function(response) {

}).

//separated error from the response
error(function(err) {

}).

//called when there's a result, or error
response(err, response) { 

}).

//type of request: push, pull, collect, your own
push();

router.push(route[, query][, headers])

  • type - the channel broadcast a message to.
  • data - the data to push to the given route
  • options - options for the given route
    • meta - tags to use to filter out listeners

router.pull(route[, query][, headers][, callback])

same as push, but expects a response

router.channels()

returns route expression

request.write(chunk)

Initializes a streamed response. Great for sending files

request.end([chunk])

Ends a response

request.hasNext()

Returns TRUE if there's a listener after the current one.

request.next()

Moves onto the next route.