10/29
2012

As Model-View-Controller(MVC) has became one of preferred architectural pattern for web applications, many open source frameworks are being developed using this architecture. RailwayJS(RJS) is one such open source web application framework for nodeJS platform, It is inspired by Ruby on Rails framework. RJS is based on another popular web-app framework expressJS.

In short RailwayJS is a MVC framework, written in JavaScript based on ExpressJS and runs over nodeJS platform.


MVC Architecture:

Model(M) in MVC architecture stands for data access and manipulation for the application. Model will work close with the database and provide methods to access the data.

In case of RJS each table is represented by a model. The model has accessor methods and other user defined methods which get data from database table. These models are created automatically when database scheme is defined. It is possible to add user defined methods to these models.

MVC Architecture

View(V) manages the display of information. It displays the data provided by the model to the user.

In RJS views can be created using templates. These templates are called layouts. Depending on the request-action the the display is modified or updated.

Controller(C) acts as a middle agent between Model and view. The controllers receives commands and initiates appropriate action to change the states of model and view.

In RJS the controller has methods which are called depending on the action specified. ie., a dispatcher is used to map an action request to appropriate action method. The action method also calls view to generate respective output layout.


Routing

One of the main task of an MVC web app is to decode the request URL to controllers and actions. This is how URL is mapped to M,V,C components to generate a web page or a response. In RJS this action is called 'routing'. RJS routing is similar to expressJS. The basic routing expressions work similar to expressJS.

All the routes are defined in config/routes.js file. The file contents looks like this:


exports.routes = function (map) {
	map.resources('posts');

	map.all(':controller/:action');
	map.all(':controller/:action/:id');
};

The routes of an RJS application can be listed by running the command railway routes or simply railway r.


helper method path controller#action
posts GET /posts.:format? posts#index
posts POST /posts.:format? posts#create
new_post GET /posts/new.:format? posts#new
edit_post GET /posts/:id/edit.:format? posts#edit
post DELETE /posts/:id.:format? posts#destroy
post PUT /posts/:id.:format? posts#update
post GET /posts/:id.:format? posts#show
ALL /:controller/:action undefined#undefined
ALL /:controller/:action/:id undefined#undefined

The first column in the output gives the helpers which can be used in views and controllers to get route.

Some example are:


path_to.new_post		# /posts/new 
path_to.edit_post(1)		# /posts/1/edit 
path_to.edit_post(post)		# /posts/1/edit (in this example post = {id: 1}) 
path_to.posts			# /posts 
path_to.post(post)		# /posts/1

Method column gives the HTTP request methods.

Path columns gives the possible URL paths which will be maped to the respectinve controller-action pair mentioned in the row.


Resource based Routing

Resource-based routing provides standard mapping between HTTP verbs and controller actions.

map.resources('posts');

will provide following routes:

helper method path controller#action
posts GET /posts posts#index
posts POST /posts posts#create
new_post GET /posts/new posts#new
edit_post GET /posts/:id/edit posts#edit
post DELETE /posts/:id posts#destroy
post PUT /posts/:id posts#update
post GET /posts/:id posts#show

Routing with HTTP verb methods

In ths type, routing can be set for individual HTTP request method. For each HTTP method there is corresponding map method, which can be used to route URL to controller action.


map.get('/posts', 'posts#index'); 
map.put('/posts', 'posts#put');

will give following routes:

helper method path controller#action
posts GET /posts posts#index
posts PUT /posts posts#put

Generic Routes

The routing can be generalised for all the controllers in the application by defining generic routes.


map.all(':controller/:action');
map.all(':controller/:action/:id');

In this example ':controller', :action and ':id' map to controller, action and id of an request URL. ie., if the request is of the form /posts/show/12345678 then posts controller's show action will be called with parmaeter being 12345678, which returns contents of the post with id 12345678. If instead /blog/index is the request then blog controller's index action is called, which typically returns all the blog posts.


Notes:

  • '*' can be used for resource mapping. When a route includes '*' it represents any 0 or more number of characters.
  • In route mapping functions (resources, all, get, set, put, delete, post) the first parameter can be a regular expression.
    
    map.get(/\/about(\.html|)/, 'static_pages#about');
    map.get(/\/contact(\.html|)/, 'static_pages#contact');
    
  • Above two lines create routes for about and contact pages. Notice the first parameter is a regular expression and is not in quotes. These route server the pages when accessed like /about or /about.html and /contact or /contact.html.
  • RJS has a separate method for root mapping.

    map.root('blog#index')

    This is similar to :

    map.get('/', 'blog#index');

Aliases for resource-based routes

Resource based routes provide a way to map different paths to controller-action pair for all HTTP request methods. This type of routing also provides helpers, which can be used in controllers and view layouts. There are occassions when there is a need for aliases for helpers. For that purpose we can use as and path options to specify helper name and path.

For example if we want to use articles instead of posts as helpers we use as option like this:

map.resources('posts',{as:'articles'});

It will produce following routes:

helper method path controller#action
articles GET /posts.:format? posts#index
articles POST /posts.:format? posts#create
new_article GET /posts/new.:format? posts#new
edit_article GET /posts/:id/edit.:format? posts#edit
article DELETE /posts/:id.:format? posts#destroy
article PUT /posts/:id.:format? posts#update
article GET /posts/:id.:format? posts#show

In this case we want to route a different path to a controller-action pair then we use path option like this:

map.resources('posts',{path:'articles'});

It will produce the following oroutes:

helper method path controller#action
posts GET /articles.:format? posts#index
posts POST /articles.:format? posts#create
new_post GET /articles/new.:format? posts#new
edit_post GET /articles/:id/edit.:format? posts#edit
post DELETE /articles/:id.:format? posts#destroy
post PUT /articles/:id.:format? posts#update
post GET /articles/:id.:format? posts#show

Also we can use both the options together create a new helper path base which routes to a given controller-action pair.

map.resources('posts', {path:'articles',as:'stories'});

This will produce:

helper method path controller#action
stories GET /articles.:format? posts#index
stories POST /articles.:format? posts#create
new_story GET /articles/new.:format? posts#new
edit_story GET /articles/:id/edit.:format? posts#edit
story DELETE /articles/:id.:format? posts#destroy
story PUT /articles/:id.:format? posts#update
story GET /articles/:id.:format? posts#show

as and path options will create new routes and doesn't alter the existing routes for the resource. There fore if we have declared:


map.resources('posts');
map.resources('posts', {as: 'articles'});

then routes will be:

helper method path controller#action
posts GET /posts.:format? posts#index
posts POST /posts.:format? posts#create
new_post GET /posts/new.:format? posts#new
edit_post GET /posts/:id/edit.:format? posts#edit
post DELETE /posts/:id.:format? posts#destroy
post PUT /posts/:id.:format? posts#update
post GET /posts/:id.:format? posts#show
articles GET /posts.:format? posts#index
articles POST /posts.:format? posts#create
new_article GET /posts/new.:format? posts#new
edit_article GET /posts/:id/edit.:format? posts#edit
article DELETE /posts/:id.:format? posts#destroy
article PUT /posts/:id.:format? posts#update
article GET /posts/:id.:format? posts#show

Nested resources

In cases like where the routing of the resource depends on another resource we use route nesting. Here the the child parent resource relation is considered while routing an path to the controller-action pair.


map.resources('posts',function(post){ 
	post.resources('comments');
});

This routing map will provide the following routes:

helper method path controller#action
post_comments GET /posts/:post_id/comments.:format? comments#index
post_comments POST /posts/:post_id/comments.:format? comments#create
new_post_comment GET /posts/:post_id/comments/new.:format? comments#new
edit_post_comment GET /posts/:post_id/comments/:id/edit.:format? comments#edit
post_comment DELETE /posts/:post_id/comments/:id.:format? comments#destroy
post_comment PUT /posts/:post_id/comments/:id.:format? comments#update
post_comment GET /posts/:post_id/comments/:id.:format? comments#show
posts GET /posts.:format? posts#index
posts POST /posts.:format? posts#create
new_post GET /posts/new.:format? posts#new
edit_post GET /posts/:id/edit.:format? posts#edit
post DELETE /posts/:id.:format? posts#destroy
post PUT /posts/:id.:format? posts#update
post GET /posts/:id.:format? posts#show

Similar routing can be defined for Poll survey routing:


map.resources('polls',function(poll){ 
	poll.resources('options');
});

Note:

To access a child resource we have to first specify the parent resource with identifier.


path_to.post_comments(post)		# /posts/1/comments 
path_to.edit_post_comment(post, comment)# /posts/1/comments/10/edit 
path_to.edit_post_comment(2,300)	# /posts/2/comments/300/edit

Namespaces

Namespaces are creted to group controllers. The common use is to declare controllers for admin area.


namespace('admin',function(admin){ 
	admin.resources('users');
});

This routing rule will match with urls /admin/users, admin/users/new, and create appropriated url helpers

helper method path controller#action
admin_users GET /admin/users.:format? admin/users#index
admin_users POST /admin/users.:format? admin/users#create
new_admin_user GET /admin/users/new.:format? admin/users#new
edit_admin_user GET /admin/users/:id/edit.:format? admin/users#edit
admin_user DELETE /admin/users/:id.:format? admin/users#destroy
admin_user PUT /admin/users/:id.:format? admin/users#update
admin_user GET /admin/users/:id.:format? admin/users#show

Here we can note that admin is not a resource, it will be used in the path to specify the namespace.


Conslusion

Sepearating the components as MVC and linking them back to generate a webpage or a response is what MVC pattern of architecture does.

RailwayJS is built using this technique and makes creating an application easier. RailwayJS achives its main objective - web development without pain with its simple approach in implementing the MVC architecture. It can be used for any application as small as a blog to a complex control panel because of its simpler and extensible routing techniques.


Refernces




comments powered by Disqus