Charting a Route to Victory

Scott Walton
Marionette.js blog
Published in
6 min readJul 24, 2017

In the past five years, one of the biggest changes I’ve seen to web application architecture has been the move to single page applications (SPA). This has been driven by the improved performance of browser JS engines and the ensuing movement towards REST APIs supporting a rich client application. Determined not to revert back to the bad old days of complicated PHP websites whose navigation progress was built entirely from form POST actions (I’m looking at you dvla.gov.uk), JS apps have embraced a tool from our server-side cousins: the Router.

In this blog post, you’ll learn how routers can help you build more accessible web applications and how your choice of router can drive the architecture of your app. We’ll then look at two of the many possible routing patterns with Marionette and how you can use them to build you apps.

The Router

Just like server-side web applications, a Router decides what to show the user based on the URL in your browser window. The great thing about routers is that they bring all the advantages of classic server-driven application to your JavaScript applications: you can bookmark pages/sections of your application and allow your users to share content with friends or on social media, retaining all the advantages of the web!

User enters a URL, the Router reads it and decides what view to show

At its core, the Router is an object mapping URLs to a set of activities. It can add optional behavior such as listening for URL changes, click events, redirect users and more advanced logic you may want to program in.

Hash vs Push State

There are two major ways you can handle routing: the historic way using the browser’s hash code or using the browser’s pushState framework. The former is compatible with all browsers and requires no additional work on your backend, however all your URLs will have a hash (#) component that your server will never see. You will have no ability to optimise the information going out (e.g. pre-caching models to reduce round trips) as your server doesn’t know what page the client is actually viewing.

Using the pushState API, you can build natural-looking URLs that are indistinguishable from regular URLs. This requires you to do a little work on your backend to fully handle this (as well as a relatively modern browser) but what you get back is the ability to do some optimisation and much nicer looking URLs!

An Example Web Server Configuration

To support pushState, you need to ensure your server is aware of this. The simplest way is to configure your backend server to listen on a specific URL that starts your app e.g. /app and return the basic template for that. A sample Nginx config should handle this for you:

We route most requests through to our application server, any static requests (like our JS files) to a specific directory, and everything else just grabs our application index file. Our application will now handle the rest of the routing.

Marionette Routing Strategies

When it comes to structuring your application around a router, there are two overarching ways to use routes: as bookmarks to get back to specific screens or as the driver of your entire application.

Bookmark-driven Architecture

By default, Backbone subscribes to the bookmarking philosophy. In this model, your application works and loads views as normal. When you want to mark a point that will be routed back to later, you do Backbone.history.navigate('path'); to update your browser’s URL. Your Router must be configured so, when Backbone.history.start() is called on your app load, the correct models are fetched and the right views are loaded.

Updating the URL comes last here

The upside of this is that your app can be built as usually would, with hooks left open (usually in the Radio) for your Router to load the right views. The downside is that you can find yourself limiting the number of routes you create to minimize the number of different states you need to account for.

Router-driven Architecture

This architecture completely rewrites how you build Marionette apps by tying your view loading directly through your Router. Typically, this means your Router will contain the structure of your application and all views get loaded by loading new URLs. Your Router listens to all changes and reacts by displaying new views.

Changing the URL is an integral part of the flow

The benefit to this is that you can build a large number of routes and set your application up very precisely according to the routing structure. However, your application will look very different to a “regular” Marionette application and you have to factor in the effect of routing on the view you show the user.

Routers

There are a number of routers out there and, as long as its not tied specifically to a specific framework, you can use pretty much any Router you want with Marionette! To keep this post straightforward, I’ll only cover two routers: the default Marionette AppRouter class and Luiz Américo’s marionette.routing module built on top of cherrytree.

Backbone Router & Marionette AppRouter

The Marionette AppRouter is a slight extension of Backbone’s default Router class with some tweaks to improve the readability and make it possible to break up into modules.

Below is an example application built with the AppRouter

This structure uses the Router to load the right view when the application starts. When the application loads our view, we use Bb.history.navigate to remember our position — navigate updates the URL bar but causes no further action to be taken.

While this simple example works nicely, a more complex view structure will require more care and for you to make some choices to balance the routes you expose against your application’s complexity.

Marionette Routing

Marionette.Routing integrates the cherrytree package into Marionette by tying routes to views, inspired by the Ember.js Router. Using marionette.routing we can set up the routes for the application and set up the view logic to be executed when each route is navigated to. Any time we want to navigate to a new part of the system, we force a transition which updates the URL and triggers the routing logic to render the right views.

Under the hood, marionette.routing uses the Backbone Radio to implement its functionality. To route between different views, you either attach the RouterLink behavior to your view or call Radio.request('router', 'transitionTo', 'note.list'); and the Router will handle the rest for you.

Be aware that, as of the time of publishing this blog, marionette.routing is still a work in progress and is missing a lot of documentation. Be sure to check what’s there first before you dive deep into the rabbit hole.

Conclusion

In conclusion, most routing strategies will involve a variation on one of the above choices. For simple applications with only a few views, then Marionette.AppRouter will do what you need. However, as your application grows and you need to provide more user-visible URLs, you may want to check out more powerful libraries.

Routers tend to be quite generic so don’t feel limited to using the built-in tools! If you find a better way that works for you then please tweet me to let me know!

Other Routers

There are a couple of other routers that either work with Marionette or work as a good inspiration for building your apps:

--

--

Scott Walton
Marionette.js blog

Full Stack Developer at Hotjar, meet-up organiser and open source contributor publishing my musings and career life lessons.