July 21, 2016

Developing An Isomorphic GraphQL App With React

by João Carvalho

isomorphic_app_react

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.

Drawing

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.