flatiron is yet another framework for building web application based on nodejs. If you feel tired of connect and express, give it a try. Follow the instruction on Getting started with flatiron to create the hello world project.
var flatiron = require('flatiron'),
path = require('path'),
app = flatiron.app;
app.config.file({ file: path.join(__dirname, 'config', 'config.json') });
app.use(flatiron.plugins.http);
app.router.get('/', function () {
this.res.json({ 'hello': 'world' })
});
app.start(3000);
It looks simple, but there’s a lot behind it. By going deep into the hello world example created by flatiron, we can get a knowledge of how it works. The powerful node-inspector will help us to achieve it.
Launch node-inspector and start the app in debug mode.
$ node-inspector&
$ node --debug-brk app.js
Open localhost:8080 in Chrome, and the app will break at first line. You may now step into each function to know what exactly happens behind the page.
broadway for plugins glue
app.js:7, (anonymous function)()
app.use(flatiron.plugins.http);
broadway/app.js:160, App.prototype.use()
this.plugins[name].attach.call(this, options);
this: app
name: 'http'
this.plugins[name]: flatiron.plugins.http
flatiron/plugins/http.js:50, exports.attach()
app.router = new director.http.Router().configure({async: true});
The flatiron app inherits use method from [broadway] (http://github.com/flatiron/broadway), which is a layer to manage the plugins. broadway call the attach method of each plugin, in this example, flatiron.plugin.http. The plugin attach a new director.http.Router to the app as app.router.
director for router
registering methods
director/http/index.js:32, exports.Router()
this.extend(exports.methods.concat(['before', 'after']));
exports.methods = ['options', 'get', 'post', 'put', ...]
director/router.js:627, Router.extend()
(function(method){...}(methods[i]);
methods = ['option', 'get', 'post', ...];
director/router.js:619, Router.extend()
self[method] = function () {...; self.on.apply(...);}
self: app.router
method: 'get'
app.router comes with standard http request methods such as get, post and so on. The methods e.g. app.router.get is extended by director.Router.extend(), see the bottom of call sequence. It also use self.on.apply(...) to register the callback on the method get.
creating routes
app.js:9, (anonymous function)()
app.router.get('/', function () {...});
director/router.js:625, (anonymous function)
self.on.apply(self, extra.concat(Array.prototype.slice.call(arguments)));
extra: 'get'
arguments: ['/', function]
director/http/index.js:59, Router.prototype.on = function () {}
director.Router.prototype.on.call(this, method, path, route);
method: 'get'
path: '/'
route: function
director/router.js:216, Router.prototype.on = Router.prototype.route = function (...){}
this.insert(method, this.scope.concat(path.split(new RegExp(this.delimiter))), route);
method: 'get'
this: app.router
this.scope: []
route: function
director/router.js:565, Router.prototype.insert = function (method, path, route, parent)
parent[method] = route; // app.routes[method] = route;
method: 'get'
path: ['', '']
parent: this.routes
this: app.routes
route: function
By calling get with a path and a callback, director insert a new route internally, it will be used by dispatch when the server get a request.
dispatching
stream.js:80 Stream.prototype.pipe = function () {...; function onend() {...} }
dest.end()
events.js:88, EventEmitter.prototype.emit
listeners[i].apply(this, arg);
events.js:156, EventEmitter.prototype.once
listener.apply(this, arguments);
director/http/index.js:124, Router.prototype.dispatch = function () {...;function parseAndInvode() {...}}
self.invoke(runlist, thisArg, callback);
runlist: [function]
thisArg: {req: req, res:res}
callback: function error(){}
director/http/index.js:341, Router.prototype.invoke
_asyncEverySeries(fns, function apply(fn, next) {...}), function() {//callback});
fns: [function]
director/http/index.js:346, Router.prototype.invoke
fn.apply(thisArg, fns.captures.concat(next));
fn: function
thisArg: {req: req, res: res}
app.js:10, app.router.get('/', function() {...});
this.res.json({ 'hello': 'world' })
A notable fact is that the dispatch will parse the route and invoke a run list. One request will result in a sequence of calls. The director router is actually quite complicated. See the document on project page for details.
union for middleware
Please ignore the first few calls in broadway. It just give us the information that broadway will initialize all plugins before start.
app.js: 13, (anonymous function)
app.start(3000);
flatiron/plugins/http.js:60, exports.attach:app.start
app.init(function(){});
flatiron/app.js,31, App.prototype.init
broadway.App.prototype.init.apply(this, Array.prototype.slice.call(arguments));
broadway/app.js:127, App.prototype.init
this.bootstrapper.init(this, initPlugins);
broadway/bootstrapper.js:53, exports.init
broadway.plugins.config.init.call(app, function(err){});
broadway/plugins/config.js:44, exports.init = function(done);
return err ? done(err) : done();
done: function(err){...;initPlugins();}
broadway/app.js:111 App.prototype.init:function initPlugins()
async.forEach(Object.keys(self.initializers), initPlugin, ensureFeatures);
broadway/app.js:107 App.prototype.init:function initPlugin(plugin, next)
self.emit(['plugin', plugin, 'init']);
next();
broadway/app.js:91, App.prototype.init:function ensureFeatures()
features.ensure()
broadway/features/index.js:10, exports.ensure
return callback();
broadway/app.js:85 App.prototype.init:function onComplete()
callback();
flatiron/plugins/http.js:69 export.attach:app.start()
app.listen(port, host, callback)
flatiron/plugins/http.js:79 export.attach:app.listen()
app.server = union.createServer({
after: app.http.after,
before: app.http.before.concat(function (req, res) {
if (!app.router.dispatch(req, res, ...){...}
}),
...
});
union/lib/core.js:52, core.createServer
return http.createServer(requestHandler);
The last two calls show that union insert some callbacks(app.http.after is also an array) when creating the native http server. There’s a comparison between union and connect on the project page.
nconf for config
broadway/plugins/config.js:46, exports.init
this.config.load();
nconf/provider.js:372, Provider.prototype.load
loadBatch(getStores(), callback);
getStores(): [{/*file*/},{/*literal*/}]
nconf/provider.js:328, Provider.prototype.load:function:loadBatch(targets, done)
async.map(targets, loadStores, function (err, objs) {}
nconf/provider.js:319, Provider.prototype.load:function:loadStore(store, next)
next(null, store.loadSync())
nconf/store/file.js:126, File.prototype.loadSync
this.store = data; // data: file content of config.json
It will load the content of configuration file to this.store.
NOT in hello world
There are more component in flatiron framework, which is not mentioned in the hello world example, e.g. plate for template, resourceful for data managment, Winston for logging.