Make the Most of Your Routes

At the core of any web application is a mapping between urls and application logic; a mapping between what is in the browser's address bar and what should be displayed on the screen. Rails has routes.rb, Django has URLconf, Backbone.js has controllersBackbone.Router, and Sammy.js has Sammy.js.

We also aren't talking about arbitrary URLs here. Modern well-designed applications use clean, semantic, and probably RESTful urls. These are urls which are human-readable, have a natural hierarchy to them, and intuitively reflect the underlying data to which they relate.

So, what's the problem?

Problem: Routers Are Designed for Stateless Servers

Historically, url routing systems are designed for web servers which maintain little to no state about the client which is making the request1. A typical request/response lifecycle would look something like the following:

  1. A url is requested.
  2. The server loads all resources corresponding to the url.
  3. A response is rendered and sent back to the client.
  4. All server-side resources are released.

By not maintaining state, the server is able to drastically reduce its memory-footprint. With this scheme in mind, it makes sense for routing systems to be nothing more than a mapping from url patterns to actions, an example of which is represented in the diagram below:

Stateless routes

Here there are four different actions: index, show, edit, and comments, with corresponding url patterns. When a url for one of these actions is requested, the application must load the resource or collection, ensure that the current user has the required permissions to view the page, and render one or more templates.

These days, however, many applications are moving away from the server and into the realm of the client-side single page javascript application. Views are no longer being rendered on the server, pages are no longer being refreshed on each request, and urls are being manipulated through hashes and HTML5's pushState method. Javascript MVC frameworks such as Backbone and Ember have also arrisen to make these types of applications more pallatable to develop.

State is Ridiculously Cheap on the Client

Most client-side javascript applications probably have access to more memory than the virtual machines that serve them. Much like programming for the desktop, one of the great benefits of single-page javascript apps is that you can freely maintain as much state as you want.

Despite this, javascript routing system are still beholden to the same approach used by stateless servers. Routes are still nothing more than a primitive map from patterns to functions, and the above diagram still applies. This is barbaric. In the context of a client-side application, the url-triggered actions of loading models, checking permissions, and rendering shared layout have become extremely redundant.

Solution: Stateful Routes

A solution for this is to incorporate a more stateful approach to routing. Common state between actions can be extracted into additional states. Consider the following re-factorization:

Stateful routes

The above diagram still captures that same url structure as the first diagram, but an intermediate state has been created which encapsulates some common logic. The show, edit, and comments actions all share a parent item state. The item state has some common concerns inside of it that are shared by all its children. Namely, the loading of the resource corresponding to the id, checking read permissions, and loading a common layout.

The idea here is that each leaf state corresponds to a route and as a route is loaded, each state along its path from the root is entered. Edges between states correspond to optional url fragments. Thus, when the url is changed to items/1, both the item and the show states are loaded. Similary, when the url is changed to items/1/comments, the item and comments states are loaded.

So far you might be relatively unimpressed. Sure this is a nice declarative approach to moving some cross-cutting concerns into a better location, but it could be argued that something like controller inheritance could accomplish the same thing. Bear with me.

Stateful Transitions Are Awesome

This becomes really cool when you take into account transitioning from one state to another. For instance, consider the transition on the client from the url items/1 to items/1/edit. This corresponds to a transition from the show state to the edit state. This is captured in the diagram below:

Stateful route transition

Since both the show and edit states share a common parent state, there is no need for it to be re-entered. All of the concerns inside the item state have already been addressed and it is trivial for the client to know to just change the view.

Transitions between states can also be dicated by more than url fragments. States can be defined by whether a user is logged in, if a post has been published, if the user is an admin, etc. This makes it extremely easy to turn off or provide alternate behavior for entire groups of routes based on application state. Just for fun, here is a diagram for such a case:

Complex routes

Here, there are different routing trees depending on whether the user is logged in or not.

Ember.js RouteManager Plug

This is the part of the blog post where I shamelessly plug a project, Ember RouteManager, which is routing system for Ember.js built with the design I have given in this post. I'm not going to include any code samples here, but I recommend you check it out!

1. There do exist plenty of stateful server-side web frameworks (continuations are cool), but they could also benefit from stateful routing.

comments powered by Disqus