Single page applications or SPAs are in trend.
SPAs put emphasis on a thick JavaScript client with a thin back end. Node.js takes this further with JavaScript on the client and on the server.
Given the popularity of JavaScript, polyglot programming got put aside. What if I told you that the same code that runs on the server, also runs on the client? Yes, welcome to isomorphic programming in JavaScript.
In this take, Iʼll introduce a bar tab app using isomorphic principles. This idea takes “code reuse” to new heights. For the demo Iʼll leverage React and GraphQL to illustrate this idea. A nice screenshot of the demo is below.

For the impatient, feel free to examine the sample code on GitHub.
What Is Isomorphic?
Imagine this, JavaScript that runs both on the client and on the server. Can you think of where this starts to break down? I can think of the DOM API getting in the way for instance. Node.js also has an API not available on the client. One way to do this is to push the limits of modularity. Start by extracting what is not tight-coupled and share those modules across boundaries. React has already done the heavy lifting with a virtual DOM. The example below illustrates the concept.
var react = typeof React === 'object' ? React : require('react');
var drink = react.createClass({
render: function() {
if (this.props.drink) {
return react.createElement(
'p',
null,
react.DOM.strong(null, 'Drink: '),
react.DOM.span(null, this.props.drink));
}
return null;
}
});
if (typeof module === 'object') {
module.exports = drink;
}
This is full of isomorphic concepts. The first line, for example, checks for an object
called React
. If it does not exist we bring in the library through Node. The module.exports
at the bottom makes drink
available in Node. The browser treats drink
as a global module once you declare it. The code that creates Reactʼs component gets encapsulated through react.createClass
. Reactʼs API got built around isomorphic principles.
The virtual DOM in React gets called through react.CreateElement
. We make use of prepackaged virtual elements through react.DOM
. The this.props.drink
is a props mechanism that makes data available to the component.
Hello, renderToString()
The beauty behind isomorphic apps is the core rendering happens both on the client and the server. Once again React makes this practical with renderToString()
.
var react = require('react');
var reactDomServer = require('react-dom/server');
var tab = require('./tab');
function render(data) {
const tabElement = react.createElement(tab, data);
return reactDomServer.renderToString(tabElement);
}
This code only runs on the server. tab
is the other React component that uses drink
. Feel free to explore tabʼs source code. The tab
component like drink
runs both on the client and server. The react-dom/server
is the central package that does the rendering on the server. A similar technique happens on the client as shown below. The data
parameter is how one initiates rendering on this component.
ReactDOM.render(React.createElement(tab, {}),
document.getElementById('bar-tab'));
This time, use ReactDOM
to do any extra rendering on the client. React will take the server side HTML wrapped around a div bar-tab
. The HTML declares <div id="bar-tab">{{reactComponent}}</div>
. Feel free to explore the rest of the HTMLʼs source code.
With component rendering happening on both client and server. Can you imagine what this means for the web? I for once remember a time when JavaScript was not a guarantee on the client. In fact, many search engine crawlers and accessibility tools still do not support JavaScript. JavaScriptʼs initial purpose was to make pages dynamic and enhance the experience.
Progressive Enhancement With GraphQL
Progressive enhancement is an ancient and battle-tested technique. React handles GraphQL queries on the server as shown below.
getInitialState: function() {
return {
query: this.props.query,
drink: this.props.drink
};
}
The props
mechanism activates when you pass values into the data
parameter. GraphQL queries the data in Node:
const queryString = url.parse(req.url, true).query;
const drinkQuery = queryString['bar-query'];
graphql.graphql(schema, drinkQuery).then(function(drink) {
request.data = getViewData({
query: query,
drink: JSON.stringify(drink)
});
engine.render(request, res, renderEngine);
});
function getViewData(data) {
return {
reactComponent: react.render(data)
};
}
Feel free to explore the rest of the code, server side requests pass through the route. Here, GraphQL takes in a schema
and a drinkQuery
from the query string. The request
object is a way to make the data available to the renderEngine
. Note reactComponent
and react.render
is what ends up on server side rendering. Now I double dog dare you to turn off JavaScript! What is so radical is the React component will behave much the same way.
On the client, a similar story occurs with JavaScript. This time instead of a full page reload, do partial updates. Ajax is a common technique for this as shown below.
handleSubmit: function(e) {
e.preventDefault();
const xhr = new XMLHttpRequest();
xhr.open('GET', `/ajax?bar-query=${this.state.query}`, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onload = function(eXhr) {
const xhrResponse = eXhr.target;
this.setState({
drink: xhrResponse.responseText
});
}.bind(this);
xhr.send();
}
There is a separate ajax
route that deals with Ajax calls from the client. The setState
changes the state of components through JavaScript. React uses this
as part of its state mechanism, so I bind(this)
in the Ajax callback. Feel free to explore the rest of the Ajax route that happens on the server.
With progressive enhancement, I am free to take the initial HTML and add to it. The beauty here is I get this for free with React.
Conclusion
Isomorphic apps bring back the battle tested techniques of the past.
Whatʼs most exciting is how this idea is a natural progression from SPAs. It shows what JavaScript can do when it runs both on the client and server. React and GraphQL explore only a glimpse of what is possible.
Lastly, if you're building React applications with sensitive logic, be sure to protect them against code theft and reverse-engineering by following our guide.