Lean, mean client-server Javascript architecture with AngularJS and Express

* Combining a rich client-side app with a heavy server-side framework can lead to a convoluted, opaque architecture. This article discusses a “lean” alternative in which the server exposes only data APIs. *

Recently I dove into the choppy, barely-charted waters of all-Javascript web stacks. Simple yet controversial: a rich client running in the browser with one of the many available frameworks, and a Node.js-based app providing the entire server. Although the ideological arguments surrounding server-side JS aren’t particularly interesting to me, it was fruitful to confront the question of what advantages this new setup might provide over the increasingly common combination of full-featured server frameworks (e.g., Rails) with rich JS clients. I’m not alone in experiencing this “rich server + rich client” architecture as a somewhat shaky marriage of convenience (see, for instance, Josh Owens’s polemical blog post on Why Meteor will kill Ruby on Rails), but do you get a real alternative by switching out your server components, or merely a new facade on the same scaffolding? This post follows my exploratory process as I came to an all-JS client-server architecture which I find both pragmatic and ultimately convincing as a way to combine separate existing components into client-heavy apps.

starting line: angular-express-blog

I began down the path of investigation with Brian Ford’s blog post on integrating client-side framework AngularJS with a server running Express, currently the de facto standard for Node.js webservers. One of my basic goals was to re-implement the simple blog app from Brian’s post using Coffeescript on both client and server, with an automated build process to streamline development. After trying out a few different approaches outlined in the sections below, I ended up somewhere farther away than I expected from both the original example’s architecture and the existing out-of-the-box solutions.

The original blog example can be found here. It is structured for simplicity in setting up a small app for demonstration purposes:

structure-angular-express-blog

In a nutshell, the Angular client code lives within the public/ directory, configured in public/js/app.js, and everything else represents the server code: the server launcher in /app.js as well as support directories for view templates, API actions, and other request-based actions. A request for the server’s root URL ‘/’ pulls up the blog posts index with roughly the following (simplified) chain of actions:

  1. browser: request ‘/’ from server
  2. express server: action routes.index: render views/index.jade (+ layout.jade) and serve as HTML
  3. browser: fetch Angular code, app JS and CSS from server (linked in HTML)
  4. angular client: controller IndexCtrl: request partial partials/index from server
  5. express server: action routes.partials: render views/partials/index.jade and serve as HTML
  6. angular client: controller IndexCtrl: inject partial HTML into DOM
  7. angular client: controller IndexCtrl: request /api/posts from server
  8. express server: action api.posts: convert posts to summary format and serve as JSON
  9. angular client: controller IndexCtrl: inject posts data into rendered HTML

There’s a lot of back and forth, as is typical of client-side apps which rely heavily on API communication. On subsequent view changes within the Angular app, the rendered partials are no longer re-fetched from the server, having been cached on the first call. So far so good: this is just about the same as we’d get from a Rails + Angular combo (but with Express’s plainer Sinatra-style organization on the backend). Let’s see where we can go from here.

rich and sweet: frappe

My first order of business being to set up an automated Coffeescript workflow on both client and server, I started with an easy pre-packaged Express + Coffeescript wrapper, David Weldon’s Frappe, inspired by similar projects such as Skeleton. Getting up and running with Frappe is a breeze – simply clone the Github repo, install dependencies with NPM, and your Express-Coffee server is ready to go. If you’ve spent some time with Rails then Frappe will feel warm and familiar, splitting server-side code into models, controllers, and views, offering some automagical classloading with its module config/autoload.coffee, and auto-packaging JS, CSS, and other assets with connect-assets (“in the spirit of the Rails 3.1 asset pipeline”).

Adding Angular into the mix was as simple as throwing the client-side code into app/assets/js and making sure that connect-assets concatenated everything correctly:

The Angular boilerplate here is just a Coffeescript version of the app definition from angular-express-seed. Filling out the server side of the equation meant providing a few more resources:

The blunt Sinatra-style actions of angular-express-seed have now become Rails-y controllers, separate from their routing and view components (and Frappe also comes packed with a small MVC “User” resource for demonstration, although the model layer is without persistence). The line of client-server communication for each web request remains pretty much the same as outlined above: the appserver renders all HTML in real time and, according to the type of request, serves up complete views, view partials, or JSON data. The combination of Frappe and the Angular Express Seed (available on Github as angular-frappe-seed) produces the following basic architecture, rich in server-side helpers and support code:

structure-angular-frappe-seed

seeds for brunch

Frappe is awesome (as is Sails.js, which offers some attractive unique features on top of standard MVC server architecture), but I wasn’t entirely happy with the result. Had I traded out Ruby just to try to recreate Rails in JS with a less mature supporting ecosystem? I wasn’t looking for a switch of languages; I was coming to realize that I wanted an architecture that delineated the roles of client and server code more clearly, particularly by paring down the server’s responsibilities as the front end gained complexity.

I started over again by using Brunch to fire up an Angular + Coffeescript project with angular-brunch-seed. Brunch provides a simple, lightweight, and, importantly, generic (framework-independent) means of automating web app workflows: pre-compiling, concatenating, obfuscating, and whatever other operations you want to perform to prepare your JS, CSS, images and other assets, so that you can get down to writing apps (Grunt offers another good solution, though a bit more complex). Adding Express to the seed project was easy and led ultimately to the “Fast and Pointed Brunch” template.

Brunch proved its worth immediately by relieving the appserver of its burden of rendering the view partials needed by the Angular app. All view templates are stored in the client code directory, and they can be shared between client and server merely by configuring the Express server to look there for its views:

Meanwhile, for the client, Brunch pre-renders all the Jade templates, producing a static JS file with the rendered template code ready for Angular to use. Along with stylesheets (compiled to CSS from a higher-level format like Stylus or LESS), HTML, and the remaining Javascript code, everything the client needs is neatly bundled up into the /_public directory, entirely independently of a web request context:

structure-brunch-public

Now the request chain for the blog posts index example described above would look like this:

  1. browser: request ‘/’ from server
  2. express server: action routes.index: render client/index.jade (+ layout.jade) and serve as HTML
  3. browser: fetch vendored library code, app JS, rendered JS templates, and CSS from server (linked in HTML)
  4. angular client: controller IndexCtrl: inject partial HTML into DOM
  5. angular client: controller IndexCtrl: request /api/posts from server
  6. express server: action api.posts: convert posts to summary format and serve as JSON
  7. angular client: controller IndexCtrl: inject posts data into rendered HTML

All the views remain in one place, available universally to server and client. The appserver performs only two main actions: it renders the main HTML page served to the client, and serves JSON data through the API. We’re almost there:

structure-fast-and-pointed-brunch

The server has been whittled down to almost exclusively API routes, but it is still responsible for rendering the main HTML page out of Jade templates every time a web pageload request comes in.

keep ‘em separated: client and server responsibilities

One last simple step brought me to a setup which encourages – almost enforces – the distinction between client and server functions. The impetus was a troubling warning tucked away in the documentation of the Angular Express Seed app:

Although Jade supports interpolation, you should be doing that mostly on the client. Mixing server and browser templating will convolute your app. Instead, use Jade as a syntactic sugar for HTML, and let AngularJS take care of interpolation on the browser side.

I agree with this reasoning 100%, having worked with apps where a mixture of client and server templating came at a cost of both clarity and performance. (Similar concerns, on a broader level, underlie David Heinemeier Hansson’s controversial tweet: “Current JavaScript solutions suffer from ‘Double MVC’. You need both server and client-side MVC stacks. Conceptual complexity is very high.”)

But the rationale from the seed app’s docs is merely a suggestion. I wanted to take it to its natural conclusion: if the server shouldn’t be injecting data into view templates, then there’s no reason the server should have to render templates at all. With Brunch handling the pre-compilation of any assets you want, the server can simply just hand off static HTML – in fact, the web client can live almost entirely in a CDN, freeing up your appservers to handle data API requests quickly and efficiently.

So I chopped out the Express view configuration given in the previous section. Gone! The server no longer even has the option to render templates and interpolate values. To serve up a response to a request for a full page load, it just tosses back the pre-compiled HTML index:

The final re-implementation of the Angular-Express blog example can be found here. The server in this type of setup is compact and simple, but it maintains responsibility for the application’s entire persistence layer (again for simplicity, the demo does not hook into an actual database). The client, which inevitably interacts with view elements, takes over the frontend entirely. Angular and Express work together quite easily here, but the sharp delineation of roles makes it simple to swap out frontend or backend frameworks independently of each other, for instance trading Express for Sinatra or Angular for a Backbone-based frontend. Separation of concerns in the app’s large-scale architecture provides the benefits of modularity usually seen at lower levels of code design.

This is of course just one of various new patterns for building JS apps with a server providing persistence, and I want to stress that it’s merely a pattern, which can lend itself to many implementations. I love some of the alternatives as well, which come with their own styles and idiosyncrasies and dazzling names, e.g., Meteor‘s complete client-server architecture with its seamless data bindings from MongoDB to view templates; or Firebase which provides a live online datastore for various client frameworks. The future’s looking bright for rich client-side apps!

One thought on “Lean, mean client-server Javascript architecture with AngularJS and Express

  1. Great read! Thanks for summing this up. I like that you described how your implementation evolved – to something that I also found out just recently :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">