What is an isomorphic application?

by Juampy NR

Javascript was traditionally the language of the web browser, performing computations directly on a user’s machine. This is referred to as “client-side” processing. With the advent of Node.js, JavaScript has become a compelling “server-side” language as well, which was traditionally the domain of languages like Java, Python and PHP.

In web development, an isomorphic application is one whose code (in this case, JavaScript) can run both in the server and the client. In this article we will inspect how an isomorphic application processes requests depending on where the request comes from.

We are currently revamping the front end of Lullabot.com using Node.js (a runtime environment for server side applications), React (an isomorphic JavaScript library built by Facebook), and CouchDB (a NoSQL database that has a REST API). These technologies will be the main actors in the following examples.

An isomorphic request, server side

In an isomorphic application, the first request made by the web browser is processed by the server while subsequent requests are processed by the client. We will use the following article as an example:

Sample article

When we inspect the source code (in Chrome, by hitting Ctrl + u) in the Network tab, we see HTML, CSS and images:

Article's top source HTML

And then, at the bottom of the source code, a few JavaScript files:

Article's bottom source HTML

The asset /build/bundle.js is the React web application, which contains the list of routes, templates and components required to navigate through the site. We will see further down how /build/bundle.js assumes command of the navigation through the website after the first request. Here is a step-by-step list of how this first request was processed:

  1. We entered http://localhost:3000/articles/importing-huge-databases-faster in the browser and hit Enter.
  2. The request reached the web server and was passed to a Node.js application.
  3. Node.js passed the request to React, which fetched the article’s data from CouchDB, built the full page, and returned it to Node.js.
  4. The user will see the response in HTML, while the browser downloads the React application (/build/bundle.js) asynchronously.

Here is the trick: this same request is processed differently if instead of entering the article in the address bar and hitting Enter, we navigate to the list of articles and then click on the article’s link. Let’s try it out.

An isomorphic request, client side

Now that we have the article (and the application) loaded in the browser, let’s navigate to the list of articles and click on the same article we accessed in the previous section. Here is the link to the article that we will click:

Article's link at articles' listing

And here is the response viewed using Chrome’s Developer Tools to inspect the network activity. Notice the XHR requests at the log, which use the XMLHttpRequest object:

Article's client side request

Here is how this request was processed:

  1. We clicked on the article link at /articles.
  2. React intercepted the click event and stopped propagation.
  3. React issued an XMLHttpRequest to CouchDB to fetch the article.
  4. React passed the article data to the article template and rendered it.

The main difference between this request and the one in the previous section is that this time the request did not hit our Node.js application. This is fast, efficient and made my jaw drop the first time I saw it.

Comparing server/client requests

The following screenshot summarizes the above interactions by comparing them side by side:

Comparison between server and client side requests

Notice how the network activity log varies from one to another. For the one in the left we entered http://localhost:3000/articles/importing-huge-databases-faster in the address field of our browser and hit Enter; while in the other instance we clicked on the article listed at http://localhost:3000/articles. The end result is identical for the user, but using the server-side method (which is the common way of doing things today) took 40x as long for the basic page load, as using client-side processing.

Benefits of going isomorphic

I have fallen in love with this kind of architecture. In the past, we would use AJAX and a bit of server-side code to make certain interactions with the page dynamic, but this always involved duplicating logic across both PHP and JavaScript. The most common example of this is a pager, where a direct request to ?page=2 is processed differently than a click on a pager link that takes you to page 2. Here is what we gain by building an isomorphic application:

  • Old devices can browse our site because the application returns HTML, which differs from common Single Page Applications (SPA), where the tag contains JavaScript.
  • The first page request is fast and subsequent ones are even faster; as opposed to common SPAs, where the first request is used to load the application and then a round trip is made to fetch what was requested.
  • There is less code, as it is shared by both the client and the server.

Risks of isomorphic applications

There is a considerable learning curve when building an isomorphic application for the first time. In our case, when we rebuilt Lullabot.com, only Jeff Eaton and Sally Young were familiar with how isomorphic applications worked. The rest of us had to learn along the way and—while it was a mind-blowing experience—that can be frustrating as well.

Here are some of the challenges where I found myself banging my head against the wall:

Debugging is trickier

I struggled to debug code as the first request runs in the server and the second in the client. For the former I had to set up a Node.js debugger while for the later I had to learn how Browserify packs JavaScript files in order to use the web browser’s debugger plus the React Developer Tools Chrome extension.

Bye bye JQuery

We decided not to use JQuery as when React runs in the server there is no DOM to manipulate. For cases where we had to process Rich Text we used Cheerio, a leaner alternative. This made me realize how dependent I was on JQuery when I struggled to write a POST request using XMLHttpRequest instead of $.post().

Taking into account where the code would run

I had to be aware at all times whether the code that I was writing would run just in the server or also on the client because the client doesn't have access to the file system, which excludes many Node.js popular modules from being used.

Avoid exposing sensitive data

Every module required via Browserify would end up being part of the web application (build/bundle.js). This meant that we had to be cautious on which modules to require client side. For example, I almost exposed our MailChimp API keys by mistake while writing the Newsletter component.

Managing settings

We had to manage two sets of settings: server side settings (such as API keys and other credentials) and client side settings (such as the hostname of CouchDB, ElasticSearch and other resources). Some of these settings vary in local, development and production environments and we were not sure of how to make it easy to manage that. We found a solution by mixing the envify and dotenv modules.

Conclusion

There are advantages and risks when writing isomorphic applications. The technology is certainly exciting and I encourage you to try any of the available isomorphic libraries and see where it fits best in your scenario. Then, feel free to post your thoughts in a comment below.

Thanks to Andrew Berry, Seth Brown and Sally Young for the feedback given while writing this article.

newsletter-bot