This post is going to be in a slightly different format to the other, more step-by-step, ones. I think the title “How to build isomorphic web apps” would be misleading, in that I’d be advocating my own dogmatic opinion. This post will simply aim to illustrate different tactics and approaches I considered and explored whilst trying to achieve a certain style of web app architecture.
What is it? Well, the dictionary definition of it is:
Isomorphic; being of identical or similar form, shape, or structure
In real, tangible terms, we want one codebase. One set of view logic. One set of routes. And have all the actual “app code” we write to be environment-agnostic, ie. the logic doesn’t need to be concerned with where it’s being ran, it can be executed on the server and on the client. Great.
- Backbone.js - We’ll use this to structure our data logic. And to also handle URL changes and routes.
- Node.js - This will create our webserver and manage incoming requests.
- Browserify - This magical tool allows us to share logic between server & client, by packaging up modules for the browser and Node.js alike.
You may ask, “why React.js?”, as we’re using Backbone.js (which has a View layer itself). Won’t they conflict? Well, maybe, but the real reason we’re using React.js, is that it can be ran in a Node.js environment and give us the rendered HTML to send along with the initial request.
The source: https://github.com/benhowdle89/isomorphic-js-app. Feel free to skip ahead and look through the codebase at your own pace.
I should also point out that I’ll try and walkthrough the application as chronologically sound as I can, ie. user hitting the site, through to displaying data, etc…but I’ll more than likely skip a few bits to avoid making this post too dense.
We only really have one single route handler in the Node.js, and it’s only used to pass off responsibility to our main app controller:
This instructs Express to accept any request that hasn’t already been handled, and call the
init function of our app controller.
init function looks like this:
And then the
respond method is where we actually send the
index.html file along with the initial markup/data:
index.html file being very minimal in nature:
If we turn to our Backbone.js router now, and look at what would happen if the user hit the site on
showUsers function is the handler for that route:
So we’re telling the
renderApp function to take in a
UsersComponent (this is our first involvement from React.js) and populate it into our main AppComponent. We can also pass in an optional Backbone Collection which will supply our route-specific data. This means we can avoid page-load AJAX requests.
In terms of getting the markup rendered, the routes handled and the components formed, the above covers a large part of the “glue logic” we had to write to enable the isomorphic nature of this web app.
App Component (React)
To illustrate a Route changing, ie. going from
/products, this is our main App (React) component:
Bootstrapping our app with data
One last thing I wanted to go through, was avoiding the page-load AJAX requests you see in most client-side web apps.
We pass our React components a Backbone collection. If that component is the first one to be rendered, ie. the user has hit
/users and we send the relevant HTML along with the initial response, we could also take that opportunity to bootstrap our collections with data, and not have to AJAX the data in on page load.
In our child components (users, products, etc) we use React’s
componentWillMount to pre-fill our Backbone collection (server-side). If the user clicks a link to go to
/users, then this piece of code will trigger an AJAX request.
We create ourselves a base Backbone Collection. This is the Collection that all other Collections extend and inherit from. So when we do
users.bootstrap(), we’re actually calling the
bootstrap method below on the base Collection.
This was my first experiment with React.js, so I was very much learning by doing, but it was certainly eye-opening to see how, with a small bit of glue logic, we could set ourselves up with a framework that allows us to write environment-agnostic code.
Myself, along with others I’m sure, find certain tutorials and blog posts fairly contrived in the examples they showcase. TodoMVC is a delightful resource, and taking nothing away from work that’s gone into it, it does exactly what it set out to do; giving you a sampling of each framework and library by building one, very simple application. By including the Users and Products resources (you can further check out the Node.js setup I usually employ when dealing with API-side data), I hope I’ve given you a glimpse at how I’d approach a real application. I can say with confidence that other applications I’ve built will, and have, scaled well, however, as this was a new and experimental architecture for me, I can’t promise the sample application I built takes into account all scenarios and situations.