Interactive web clients: frontend routing or backend routing?

As frontend web frameworks like AngularJS, BackboneJS and EmberJS (and ReactJS, though ReactJS doesn’t give you MVC, only V) gain prominence, a question that’s often asked and explored is — where do we specify our routing?

Frontend MVC-style JS frameworks usually come with libraries that help you define url routes.  And frontend MVC-style JS frameworks are often coupled with NodeJS, where NodeJS can act as the backend server to provide you a way to specify your url routes like Django’s urls.py does or Rails’ routes.rb or martini-go’s m.[http method GET, PUT, POST, DELETE, PATCH] do.  Typically, if we implement backend routing in NodeJS, it will be represented as such:

// ... more code above, truncate code here

var app = connect()
     .use(express.static(__dirname+"/build"))
     .use(express.static(__dirname+"/bower_components"))
     .use( function(req, res) {
var pathname=url.parse(req.url).pathname;
var localpath = path.resolve(__dirname);
switch(pathname){
    case '/register':
        getfile(__dirname+"/html/register.html", res, "text/html");
        break;
    case '/homepage':
        getfile(__dirname+"/html/homepage.html", res, "text/html");
        break;
    case '/upload':
        getfile(__dirname+"/html/upload.html", res, "text/html");
        break;
    default:
        getfile(__dirname+"/html/index.html", res, "text/html");
        break;
}
});

http.createServer(app).listen(8000, serverUrl);

which as you can see, is fundamentally analogous to the framework interfaces provided by python/django or python/flask or ruby/rails or golang/martini-go.

In short, if we so choose to use nodejs for backend routing when implementing our web client, we are essentially asking our server backend for url route definitions.

This dilutes the benefits associated with implementing a frontend-only web client because we will in fact be polling our server for a URL whenever we render a new “page” (screen).  Put it another way – would you implement an iOS or Android app that needs to ask the server which screen to transit to whenever a user needs to load a new screen?

Viewed in this context, the answer is clear — we should be using frontend routing to leverage on the full benefits provided by feature-complete MVC JS framework like AngularJS or BackboneJS or EmberJS.  This means that the end user of our web app only loads our html/js/css files once.  Dynamic data on each “screen/page” is retrieved from a REST API and has nothing to do with the “screens/pages” already implemented in the frontend, which is why all routes are *already decided* by our frontend routing the first (and-only) time your user’s web browser loads up the site.  This is also why we often refer to these type of web apps as “single page app”.

Because of frontend routing via these JS frameworks, we will see a strange hashbang symbol in our url, like this http://ourdomain.com/#/login .  Such urls are not cool in my books and I want my pretty urls!  So sad… fortunately for us, HTML5’s pushstate feature is readily available and already implemented in all these modern MVC JS framework. With a little configuration, we can have our pretty urls without the hashbang-ed urls.

Like this:-

AngularJS – http://scotch.io/quick-tips/js/angular/pretty-urls-in-angularjs-removing-the-hashtag

BackboneJS – http://zachlendon.github.io/blog/2012/02/21/backbone-dot-js-from-hashbangs-to-pushstate/

What about my SEO?

The astute reader and seasoned web developer/owner will ask a question at this point — if it’s a “single page app” that is fundamentally rendered by hashbang urls, wouldn’t our SEO be screwed?

Great question, but developers of these frameworks are seasoned web veterans…

Handling SEO is nothing but providing a way for search engine crawlers (“bots”) to navigate your website and associate content/keywords on each “url” on your website so that search engine users can find your content. Here’s a great article detailing how to “serve” content to the bots while providing the seamless experience provided by a single-page app to the human user – http://backbonetutorials.com/seo-for-single-page-apps/

Long story short, we will *still* deploy a nodejs app via phantomjs with generated “backend routing” solely to serve our bots.  As far as our human friends are concerned, they will never need to access these content or poll the server to figure out url routes.  Best of both worlds.

 

python virtualenv with node environment via nodeenv

Virtualenvwrapper is one of the most useful tools a python user should be familiar with.  Built on top of virtualenv, it helps us avoid a lot of redundant “code administrative” work and simplifies our 3rd party python package isolation.

NodeJS has a similar isolation mechanism and a number of such isolation tools available.

I prefer to use the nodeenv tool because it saves me the trouble of typing out long “source” commands (much like virtualenvwrapper helps me avoid typing in long “source” commands provided by the virtualenv tool).  Also, the integration with virtualenvwrapper implies that I don’t have to waste time remembering new commands to manage my isolated nodejs environment.

So here’s how it’s done. Really efficiently! (assuming of course you have installed your python virtualenvwrapper and virtualenv tools properly):

$ mkvirtualenv myproject1
$ pip install nodeenv  # This installs the nodeenv package into our new python virtualenv so named "myproject1" above
$ nodeenv -p  # This commands installs nodejs and adds new shell functions to our virtualenvwrapper shell functions
* Install node.js (0.10.12) ..
* Appending nodeenv settings to /Users/calvin/.virtualenvs/myproject1/bin/activate
$ deactivate; workon project1   # Deactivate and re-activate to ensure we load in the updated shell functions and environment

Once this is done, we are all set with project1 being our nodejs AND python isolated environment.  When we use the `npm install -g` command, we install our npm packages into our node virtual env.

$ npm install -g yo grunt-cli bower

$ yo angular
[?] Would you like to include Twitter Bootstrap?: No
[?] Would you like to include angular-resource.js?: Yes
[?] Would you like to include angular-cookies.js?: Yes
[?] Would you like to include angular-sanitize.js?: Yes

This installs the `yo`, `grunt-cli` and `bower` tools directly into our node virtual environment and using `yo angular` we easily scaffold out a new angularjs project.

A quick peek at our ~/.virtualenvs/myproject1 directory shows us the directory structure that includes python packages and npm packages.


$ cd ~/.virtualenvs/myproject1

$ tree -L 2
.
├── bin
│   ├── activate
│   ├── activate.csh
│   ├── activate.fish
│   ├── activate_this.py
│   ├── bower -> ../lib/node_modules/bower/bin/bower
│   ├── easy_install
│   ├── easy_install-2.7
│   ├── get_env_details
│   ├── grunt -> ../lib/node_modules/grunt-cli/bin/grunt
│   ├── istanbul -> ../lib/node_modules/istanbul/lib/cli.js
│   ├── karma -> ../lib/node_modules/karma/bin/karma
│   ├── node
│   ├── nodeenv
│   ├── npm -> ../lib/node_modules/npm/bin/npm-cli.js
│   ├── pip
│   ├── pip-2.7
│   ├── postactivate
│   ├── postdeactivate
│   ├── preactivate
│   ├── predeactivate
│   ├── python
│   ├── python2 -> python
│   ├── python2.7 -> python
│   └── yo -> ../lib/node_modules/yo/bin/yo
├── include
│   └── python2.7 -> /opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7
├── install.cfg
├── lib
│   ├── dtrace
│   ├── node_modules
│   └── python2.7
├── share
│   └── man
├── src
│   └── node-v0.10.12
└── tags

That’s it.

compass/zurb-foundation via gem with yeoman/Gruntfile.js

Starting a frontend project (e.g. an AngularJS project) with yeoman helps us generate a bunch of default directory layout and a pre-set Gruntfile.js configuration file. This configuration file contains most of the necessary settings that controls our project requirements and behaviour.

In this post, I will explain how I get compass and zurb-foundation running seamlessly as scss imports in my yeoman-generated project.


grunt.initConfig({
 yeoman: yeomanConfig,
 watch: {
 coffee: {
 files: ['<%= yeoman.app %>/scripts/{,*/}*.coffee'],
 tasks: ['coffee:dist']
 },
 coffeeTest: {
 files: ['test/spec/{,*/}*.coffee'],
 tasks: ['coffee:test']
 },
 compass: {
 files: ['<%= yeoman.app %>/styles/{,*/}*.{scss,sass}'],
 tasks: ['compass']
 },
 livereload: {
 files: [
 '<%= yeoman.app %>/{,*/}*.html',
 '{.tmp,<%= yeoman.app %>}/styles/{,*/}*.css',
 '{.tmp,<%= yeoman.app %>}/scripts/{,*/}*.js',
 '<%= yeoman.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}'
 ],
 tasks: ['livereload']
 }
 },

Update our configuration for compass

compass: {
 options: {
 config: '.compass.rb',
 force: true
 },
 dist: {},
 server: {
 options: {
 debugInfo: true
 }
 }
 },

.compass.rb is references in a new file we create with the following:

require 'zurb-foundation'

http_path = "/"
sass_dir = 'app/styles'
css_dir = '.tmp/styles'
images_dir = 'app/images'
javascript_dir = 'app/scripts'
fonts_dir = 'app/styles/fonts'
import_path = 'app/components'

Since this is a ruby file, it loads up zurb-foundation in its first line.

We install zurb-foundation with gem as follows (if you do not have rvm or gem correctly configured, check out my previous post guiding you to set up rvm step-by-step).

$ rvm use 1.9.3   # if we have not done this yet
$ gem install zurb-foundation

Once we have zurb-foundation installed, it is trivial to import the zurb-foundation framework into our scss file.

@import "foundation"

Quite a bit of hassle to get to this point but now we can mess with zurb-foundation framework in our project!

Zurb foundation includes a whole range of scss and we are interested in its location so that we can specify the appropriate @import statement to use the appropriate components to style our application.  Here’s how we find the location of our recently installed zurb-foundation library.

$ irb
1.9.3-p448 :001 > spec = Gem::Specification.find_by_name("zurb-foundation")
=> #<Gem::Specification:0x3fe2b185bb40 zurb-foundation-4.2.3>
1.9.3-p448 :002 > gem_root = spec.gem_dir
=> "/Users/calvin/.rvm/gems/ruby-1.9.3-p448/gems/zurb-foundation-4.2.3"
1.9.3-p448 :003 >

So, if we change into this directory,

$ cd /Users/calvin/.rvm/gems/ruby-1.9.3-p448/gems/zurb-foundation-4.2.3
$ tree scss

scss
├── foundation
│ ├── _variables.scss
│ └── components
│      ├── _alert-boxes.scss
│      ├── _block-grid.scss
│      ├── _breadcrumbs.scss
│      ├── _button-groups.scss
│      ├── _buttons.scss
│      ├── _clearing.scss
│      ├── _custom-forms.scss
│      ├── _dropdown-buttons.scss
│      ├── _dropdown.scss
│      ├── _flex-video.scss
│      ├── _forms.scss
│      ├── _global.scss
│      ├── _grid.scss
│      ├── _inline-lists.scss
│      ├── _joyride.scss
│      ├── _keystrokes.scss
│      ├── _labels.scss
│      ├── _magellan.scss
│      ├── _orbit.scss
│      ├── _pagination.scss
│      ├── _panels.scss
│      ├── _pricing-tables.scss
│      ├── _progress-bars.scss
│      ├── _reveal.scss
│      ├── _section.scss
│      ├── _side-nav.scss
│      ├── _split-buttons.scss
│      ├── _sub-nav.scss
│      ├── _switch.scss
│      ├── _tables.scss
│      ├── _thumbs.scss
│      ├── _tooltips.scss
│      ├── _top-bar.scss
│      ├── _type.scss
│      └── _visibility.scss
├── foundation.scss
└── normalize.scss

which gives us the list of zurb foundation scss components we can import into our “main.scss” file!