7 min read

In this two part post series, we will walk through how to write a universal (or isomorphic) JavaScript app. This first part will cover what a universal JavaScript application is, why it is such an exciting concept, and the first two steps for creating the app, which are serving post data and adding React. In Part 2 of this series we walk through steps 3-6, which are client-side routing with React Router, server rendering, data flow refactoring, and data loading of the app.

What is a Universal JavaScript app?

To put it simply, a universal JavaScript app is an application that can render itself on the client and the server. It combines the features of traditional server-side MVC frameworks (Rails, ASP.NET MVC, and Spring MVC), where markup is generated on the server and sent to the client, with the features of SPA frameworks (Angular, Ember, Backbone, and so on), where the server is only responsible for the data and the client generates markup.

Universal or Isomorphic?

There has been some debate in the JavaScript community over the terms “universal” and “isomorphic” to describe apps that can run on the client and server. I personally prefer the term “universal,” simply because it’s a more familiar word and makes the concept easier to understand. If you’re interested in this discussion, you can read the below articles:

What are the advantages?

Switching between one language on the server and JavaScript on the client can harm your productivity. JavaScript is a unique language that, for better or worse, behaves in a very different way from most server-side languages. Writing universal JavaScript apps allows you to simplify your workflow and immerse yourself in JavaScript. If you’re writing a web application today, chances are that you’re writing a lot of JavaScript anyway. Why not dive in? Node continues to improve with better performance and more features thanks to V8 and it’s well run community, and npm is a fantastic package manager with thousands of quality packages available. There is tremendous brain power being devoted to JavaScript right now. Take advantage of it!

On top of that, maintainability of a universal app is better because it allows more code reuse. How many times have you implemented the same validation logic in your server and front end code? Or rewritten utility functions? With some careful architecture and decoupling, you can write and test code once that will work on the server and client.

Performance

SPAs are great because they allow the user to navigate applications without waiting for full pages to be sent down from the server. The cost, however, is longer wait times for the application to be initialized on the first load because the browser needs to receive all the assets needed to run the full app up front. What if there are rarely visited areas in your app? Why should every client have to wait for the logic and assets needed for those areas? This was the problem Netflix solved using universal JavaScript.

MVC apps have the inverse problem. Each page only has the markup, assets, and JavaScript needed for that page, but the trade-off is round trips to the server for every page.

SEO

Another disadvantage of SPAs is their weakness on SEO. Although web crawlers are getting better at understanding JavaScript, a site generated on the server will always be superior. With universal JavaScript, any public-facing page on your site can be easily requested and indexed by search engines.

Building an Example Universal JavaScript App

Now that we’ve gained some background on universal JavaScript apps, let’s walk through building a very simple blog website as an example. Here are the tools we’ll use:

Express
React
React Router
Babel
Webpack

I’ve chosen these tools because of their popularity and ease of accomplishing our task. I won’t be covering how to use Redux or other Flux implementations because, while useful in a production application, they are not necessary for demoing how to create a universal app.

To keep things simple, we will forgo a database and just store our data in a flat file. We’ll also keep the Webpack shenanigans to a minimum and only do what is necessary to transpile and bundle our code.

You can grab the code for this walkthrough at here, and follow along. There are branches for each step along the way. Be sure to run npm install for each step.

Let’s get started!

Step 1: Serving Post Data

git checkout serving-post-data && npm install

We’re going to start off slow, and simply set up the data we want to serve. Our posts are stored in the posts.js file, and we just have a simple Express server in server.js that takes requests at /api/post/{id}. Snippets of these files are below.

// posts.js
module.exports = [
  ...
  {
    id: 2,
    title: 'Expert Node',
    slug: 'expert-node',
    content: 'Street art 8-bit photo booth, aesthetic kickstarter organic raw denim hoodie non kale chips pour-over occaecat. Banjo non ea, enim assumenda forage excepteur typewriter dolore ullamco. Pickled meggings dreamcatcher ugh, church-key brooklyn portland freegan normcore meditation tacos aute chicharrones skateboard polaroid. Delectus affogato assumenda heirloom sed, do squid aute voluptate sartorial. Roof party drinking vinegar franzen mixtape meditation asymmetrical. Yuccie flexitarian est accusamus, yr 3 wolf moon aliqua mumblecore waistcoat freegan shabby chic. Irure 90's commodo, letterpress nostrud echo park cray assumenda stumptown lumbersexual magna microdosing slow-carb dreamcatcher bicycle rights. Scenester sartorial duis, pop-up etsy sed man bun art party bicycle rights delectus fixie enim. Master cleanse esse exercitation, twee pariatur venmo eu sed ethical. Plaid freegan chambray, man braid aesthetic swag exercitation godard schlitz. Esse placeat VHS knausgaard fashion axe cred. In cray selvage, waistcoat 8-bit excepteur duis schlitz. Before they sold out bicycle rights fixie excepteur, drinking vinegar normcore laboris 90's cliche aliqua 8-bit hoodie post-ironic. Seitan tattooed thundercats, kinfolk consectetur etsy veniam tofu enim pour-over narwhal hammock plaid.'
  },
...
]

// server.js
...

app.get('/api/post/:id?', (req, res) => {
  const id = req.params.id
  if (!id) {
    res.send(posts)
  } else {
    const post = posts.find(p => p.id == id);
    if (post)
      res.send(post)
    else
      res.status(404).send('Not Found')
  }
})

...

You can start the server by running node server.js, and then request all posts by going to localhost:3000/api/post or a single post by id such as localhost:3000/api/post/0. Great! Let’s move on.

Step 2: Add React

git checkout add-react && npm install

Now that we have the data exposed via a simple web service, let’s use React to render a list of posts on the page. Before we get there, however, we need to set up webpack to transpile and bundle our code. Below is our simple webpack.config.js to do this:

// webpack.config.js
var webpack = require('webpack')

module.exports = {
  entry: './index.js',

  output: {
    path: 'public',
    filename: 'bundle.js'
  },

  module: {
    loaders: [
      { test: /.js$/, exclude: /node_modules/, loader: 'babel-loader?presets[]=es2015&presets[]=react' }
    ]
  }
}

All we’re doing is bundling our code with index.js as an entry point and writing the bundle to a public folder that will be served by Express. Speaking of index.js, here it is:

// index.js
import React from 'react'
import { render } from 'react-dom'
import App from './components/app'

render (
  <App />, document.getElementById('app')
)

And finally, we have App.js:

// components/App.js
import React from 'react'

const allPostsUrl = '/api/post'

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      posts: []
    }
  }

  componentDidMount() {
    const request = new XMLHttpRequest()
    request.open('GET', allPostsUrl, true)
    request.setRequestHeader('Content-type', 'application/json');

    request.onload = () => {
      if (request.status === 200) {
        this.setState({
          posts: JSON.parse(request.response)
        });
      }
    }

    request.send();
  }

  render() {
    const posts = this.state.posts.map((post) => {
      return <li key={post.id}>{post.title}</li>
    })

    return (
      <div>
        <h3>Posts</h3>
        <ul>
          {posts}
        </ul>
      </div>
    )
  }
}

export default App

Once the App component is mounted, it sends a request for the posts, and renders them as a list. To see this step in action, build the webpack bundle first with npm run build:client. Then, you can run node server.js just like before.

http://localhost:3000 will now display a list of our posts.

Conclusion

Now that React has been added, take a look at Part 2 where we cover client-side routing with React Router, server rendering, data flow refactoring, and data loading of the app.

About the author

John Oerter is a software engineer from Omaha, Nebraska, USA. He has a passion for continuous improvement and learning in all areas of software development, including Docker, JavaScript, and C#. He blogs at here.

LEAVE A REPLY

Please enter your comment!
Please enter your name here