sunnuntai 31. toukokuuta 2015

Isomorphic React.js with Go backend

Go is a great language for making web sites. Something in component based React is absolutely brilliant after getting to know bloat Angularjs. Combining Go for server side rendering and api backend, and React for client side rendering sounds like a dream come true. No need for prerendering services which is really really silly (First we make a client side rendering website and then we can't use it for everything).

The code which I'm writing here is running at http://isomorphic.uutispuro.fi/.

I had earlier come across Echo, (Echo is a fast HTTP router (zero memory allocation) and micro web framework in Go), which I had to try.

import "github.com/labstack/echo"
e := echo.New()

// server side rendered pages
e.Get("/", index)
e.Get("/anotherpage", anotherpage)

// static files
e.Static("/public/js", "public/js")
e.Static("/public/css", "public/css")

// backend api proxy
e.Get("/api/frontpage", apiFrontPage)
e.Get("/api/anotherpage", apiAnotherPage)

e.Run(":3000")

Quite simple really. Easy to add gzip and other middleware.

A little illustration of what we are doing


Server side rendering

We will get a RSS feed from BBC scheduled and in the background and save the fetched feed and it's rendered version for later use.

First the scheduling:
func tick() {
  // schelude ticker for every 70 seconds
  ticker := time.NewTicker(70 * time.Second)
  for {
    // fetch the rss feed
    fetchFeed()
    // render the rss feed
    reactIndex()
    <-ticker.C
  }
}

Rendering:
func reactIndex() {
  v := newRenderer([]string{"public/js/frontpage.js", "public/js/common.js"}).
  runCmd(`
    var data = ` + rss + `;
    React.renderToString(News({'data' : data}));
  `)
  sValue, err := v.ToString()
  if err != nil {
    fmt.Println(err)
  }
  frontPageRendered = sValue
}

We make a newRenderer to which we give two needed javascript files and then run the runCmd which interprets the javascript into html. This is done with React ids, so that when the same is done in the client, rendering doesn't need to be done again.


Client side rendering

What we do at the client end to start up React and how do we handle going from page to an other?

We add a client.js into use for the browser.

var xmlhttp = new XMLHttpRequest();

var route = function() {
  if (window.location.pathname === '/') {
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
        var json = xmlhttp.responseText;
        React.render(News({'data' : JSON.parse(json)}), document.getElementById('body'));
      }
    }
    xmlhttp.open("GET", "/api/frontpage", true);
    xmlhttp.send();
  }

  if (window.location.pathname === '/anotherpage') {
    xmlhttp.onreadystatechange = function() {
      if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
        var json = xmlhttp.responseText;
        React.render(Another({'data' : JSON.parse(json)}), document.getElementById('body'));
      }
    }
    xmlhttp.open("GET", "/api/anotherpage", true);
    xmlhttp.send();
  }
}
route();


We have two api routes, one for root page and one for the second page. Both routes get json from api endpoints. After the function we call for the initial React check (React.render). The rendering would actually be enough after full page load, but this is a simplified solution.

At frontpage.js and anotherpage.js we have the navigation links (duplicate code, should be a components on its own).

React.DOM.div({className: "pure-menu pure-menu-horizontal"},
    React.createElement("a", {
        className: "pure-menu-heading pure-menu-link",
        href: "/",
        onClick: clickHandler
      }, "Home"),
    React.DOM.ul({className: "pure-menu-list"},
      React.DOM.li({className: "pure-menu-item"},
        React.createElement("a", {
        href: "/anotherpage",
        onClick: clickHandler
      }, "Another page")
    )
  )
)


Clicks for these navigation links call for clickHandler which is in common.js

var clickHandler = function(e) {
  e.preventDefault()
  history.pushState({}, "", e.target.href);
  route();
}


First we disable making the whole page load, second we make history.pushState for altering the browser location bar, and finally call for route to change the page content according to changed location.

You can view the whole code at https://github.com/jelinden/go-isomorphic-react and see it in action at http://isomorphic.uutispuro.fi/.

If someone uses this for own purposes, bare in mind that there are many unfinished things. Client side is made so that "it works". Doesn't mean that everything is polished or that everything works. For example entering backspace doesn't load the page content as it should.

Another major setback is the fact that the javascript interpreter is way too slow. You can't use it to render server side on the fly. On my Mac rendering front page takes about 4 seconds.





4 kommenttia:

  1. Blogin hallinnoija on poistanut tämän kommentin.

    VastaaPoista
  2. The content is good and very informative and I personally thank you for sharing React.js articles.

    VastaaPoista
  3. Blogin hallinnoija on poistanut tämän kommentin.

    VastaaPoista
  4. Blogin hallinnoija on poistanut tämän kommentin.

    VastaaPoista

Huomaa: vain tämän blogin jäsen voi lisätä kommentin.