Documentation

bricks is very modular, and provides a great deal of flexibility. Almost every aspect of bricks can be modified, giving you an extremely powerful application framework.

Routes

Routes separation is an important concept within bricks. Unlike other application frameworks, routing is broken into four sections; each section has its own set of routes that can be run, each with different capabilities. Route separation allows for multiple matches to the same path to be executed at different points during the request.

Routes are always traversed in the same order: pre, main, post, and final. Unless specifically skipped over by calling response.final(), all routes will be run sequentially. A section can be specified while adding a route. If you do not specify a section, the route will be added to the main route.


    appserver.addRoute("/static/.+", appServer.plugins.filehandler, { section: "main" });
    appserver.addRoute(".+", appServer.plugins.loghandler, { section: "final" });

Pre

The first route is the pre route. The pre route is responsible for initial request and response manipulation, including but not limited to cookies, session handling, loading a user, aliasing, and redirects.

Main

This is the main route. It is default route, and is responsible for normal application runs. This is the route that should be used for normal client output.

Post

The post route is responsible for final manipulation of the output before data is sent to the requesting client. This is where final headers can be set, and manipulation such as replacing text, or gzip occurs.

Once the post route has been run, the buffers are written to the requesting client, and response.write() will throw an exception if written to.

Final

The final route is responsible for request and response accounting. This is route is read-only, and should be used for any logging or accounting that needs to be completed.

The Anatomy of a Route

A route is added to the bricks appserver via appserver.addRoute().


    appserver.addRoute("/static/.+", appServer.plugins.filehandler, { basedir: "./htdocs" });
    appserver.addRoute(".+", appServer.plugins.fourohfour);

Route Matching

Route matching occurs in the order that routes have been added. Decisions whether each route will be executed is based on a simple true or false truth value. Matching only occurs on the path, and does not take into account URL parameters.

String Matches

String matching occurs using String.match(). Either static strings or regular expressions can be used to match a route.


    appserver.addRoute("/ajax/api", apiHandler);
    appserver.addRoute("^/index\.(htm|html)$", indexHandler);

Regular Expressions

Matches may also occur with regular expressions.


    var re = new RegExp("^/index\.(htm|html)$");
    appserver.addRoute(re, indexHandler);

Functions

Function matches also work on truth values.


    function checkPost(path, request) {
      if (path === '/api/user' && request.method === 'GET') {
        return true;
      }
      return false;
    }

Advanced Routing

By default, a route is checked against the defaultRouteChecker(). It is possible to override this at application server setup. With this you can completely change how routes are matched. This does not change the number of routes, nor the order that they are executed.


    function alternateCheckRoute(route, path, request) {
      if (typeof(route) === 'function') {
          var match = route(path);
          if (match) {
              return true;
          }
      } else {
          if ((typeof(route) === 'string') && path.match(route)) {
              return true;
          }
      }

      return false;
    }
    
    var appserver = new bricks.appserver({ checkRoute: alternateCheckRoute });

Execution

Once a route has been matched, execution occurs. Execution can be against either a function or a plugin.


    appserver.addRoute("/static/.+", appserver.plugins.fileserver);
    appserver.addRoute(".+", function (request, response) { response.write("Hello World"); });

Functions

When a function is added to the route, it is executed directly once the route is determined as matched. A function is passed the request, response and any options passed during route addition.


    function hello(request, response, options) {
      response.write("Hello World");
      response.end();
    }
    
    appserver.addRoute("/hello", hello);

Plugins

Plugins are a little more advanced than using a function. There is an initialization function, as well as a plugin function. This allows for more advanced stateful operation. Upon addition the the route, the init function is executed. Upon route execution, the plugin function is executed.

Built-In

There are a small number of built-in plugins that are shipped by default with bricks. These plugins are meant to provide basic functionality to the application server. Built-in plugins are available as part of the application server.


    appserver.addRoute(".+", appserver.plugins.filehandler);

plugins.filehandler

The filehandler plugin serves up static files from the filesystem in a very basic manner. The filehandler options include the basedir which defaults to ./. There is no caching, or any advanced features with this plugin.


    appserver.addRoute("/static/.+", appserver.plugins.filehandler, { basedir: "./htdocs" });

plugins.fourohfour

The fourohfour plugin is a very basic 404 handler. It simply sets the response.statusCode() to 404, and returns 404 Error to the requesting client. The fourohfour plugin is typically added at the end of the main route as a catch-all.

plugins.loghandler

The loghandler is a Common Log Format (with referrer and user-agent) logging handler. The filename option sets the output file for logging. The loghandler plugin is typically added as part of the final route. The loghandler calls response.next() so is safe to add at the beginning of the final route.


    appserver.addRoute(".+", appserver.plugins.loghandler, { section: "final", filename: "access.log" });

plugins.request

The request plugin adds a little bit extra to the request object to better deal with POST, PUT, parameters, and the body.


    appserver.addRoute("^/admin/.+", appserver.plugins.request, { section: "pre" });
    
    function matchedRoute (request, response) {
      console.log("param foo: " + request.param('foo'));
      console.log("body = " + request.body);

      response.end();
    }
    
    appserver.addRoute("^/admin/.+", matchedRoute);

plugins.redirect

The redirect handler plugin handles temporary and permanent redirects. The redirect handler uses the same defaultRouteChecker() as the appserver to match paths. By default the redirects are considered temporary and response.statusCode() is set to 307. If the redirect is marked permanent, then the response.statusCode() is set to 301. The redirect handler uses options to set up its redirects. The redirect handler should be added as the first entry of the pre route.


    var redirects = {
        section: "pre",
        routes: [
          {
            path: "^/bar$",
            url:  'http://foo.com/bar'
          },
          {
            path: "^/foo$",
            url:  'http://bar.com/foo',
            permanent: true
          }
        ]
    };

    appserver.addRoute(".+", appserver.plugins.redirecthandler, redirects);

plugins.sessionhandler

The sessionhandler handles basic sessions and should be added to the beginning of the pre route. The sessionhandler exposes the session via request.session. By default it sets a cookie and stores all session data in memory.


    var options = {
      section: "pre",
      session: "choclatechip", // session name, defaults to 'bricks-session'
      sessionStore: myStorage  // defaults to in-memory session storage
    };
    
    appserver.addRoute("/secured/.+", plugins.sessionhandler, options);
    appserver.addRoute("/secured/test", function(request, response) { request.session.secured = true; });

Developing Plugins

Plugin development is simple. There are two functions for functionality: exports.init and exports.plugin. The init function is called when added to the route with any options passed for configuration.

The plugin function handles execution when the route has been matched.


    var hits;

    exports.init = function (options) {
      options = options || { };
      
      hits = options.start || 0;
    }
    
    exports.plugin = function (request, response, options) {
      response.write(hits + " hits so far.");
    }

Controlling Flow

Flow control is very straightforward. Each matching route is executed in order until either completion of the route or response.end() has been called. All flow control is available via response.

response.next()

This continues the running of the routes until the last route is reached. Flow is passed on to the next matching route.

response.end()

The response.end() call immediately ends the current route, and the next route begins (if applicable).

response.final()

Calling response.final() immediately halts execution of further routes, and begins execution of the final route. This is a very powerful call, as it has the ability to circumvent any further route matches that may need to execute response.write(). It is recommended that this function be used with caution.

Events

Events play an important part of bricks. Events allow for asynchronous actions to be executed under various circumstances, including the completion of sections and any errors.

Routing Events

Event listeners can be set on response to listen for the completion of sections. Each event will emit its own event for the end of each route. Events can have more than one listener, and can be responded to more than once.


    function mainCompletion(event, response) {
      response.write("Main Completion");
    }

    response.on('main.complete', mainCompletion);

pre.complete

Upon completion of the pre route the pre.complete event is fired.

main.complete

Upon completion of the main route the main.complete event is fired.

post.complete

Upon completion of the post route the post.complete event is fired.

final.complete

Upon completion of the final route the final.complete event is fired.

Error Events

Error events are fired upon error. There are two error events, the run.fatal event, and the route.fatal event. These events are triggered when an error occurs either during the route match, or during a route execution. Unlike route completion, these events require the registration of an error handler on the appserver itself.


    appserver.addEventHandler('route.fatal', function (error) { console.log("FATAL: " + error) });