The 3REE Stack: React + Redux + RethinkDB + Express.js

Over the past 12-24 months the velocity of change in the JavaScript world has been intense. With the rise of Universal (Isomorphic) applications, React, Flux, Webpack, IO.js and its merge back Node.js and ES2015 (ES6), it is clear that the landscape is undergoing a serious makeover. It is quite a crazy time to be a JavaScript engineer, with what seems like a new thing coming out every week that has threatened to disrupt the status quo.

It is hard to know what to pay attention to. As an enthusiast it is a very exciting time, with new toys to play with every week. But as an Engineer it can be quite frustrating when the goal posts move so quickly. However, out of this wonderfully frustrating evolution of JavaScript I think things are beginning to settle down.

The main catalyst to me feeling this way about JavaScript is the birth and rise to prominence of Redux. If you haven't yet heard about Redux then you should check out Dan Abramov's talk, the creator of Redux. It really is impressive, I've watched it like 5 times :-/. Other people are really excited about it too. [1] [2].

This blog introduces a new stack held together by Redux, that we like to call the 3REE (React + Redux + RethinkDB + Express) stack. It brings together all the technologies we have come to be amazed by and delivers a coding environment that, IOHO, is liberating and elegant.

N.B A key motivator to writing this blog was sharing with the community an approach to tying everything together in a Redux app, from server-side rendering, async communication to connecting with React components.

The 3REE stack

This stack brings together the following technologies:

React

React is a library for building user interfaces. It is a declarative approach to building the view layer of your application. You build up a hierarchy of React components that each encapsulate certain parts of your UI.

The key principles to React are:

  • composition
  • seperation of concerns
  • uni-directional data flow.

For a quick and dirty intro to React I quite like this blog post by Andrew Ray.

Redux

Redux is a state container for JavaScript applications that is based upon the principles of the Flux architecture. It takes this architectural approach and removes a lot of complexity, moving towards a more functional style.

The key principles behind Redux are:

  • single source of truth
  • immutable state
  • pure functions for state transitions (no side effects)

Released in June 2015, Redux has gained a lot of traction, quickly rising to become the 2nd most popular Flux architecture, behind Facebook's own implementation. The graph below highlights this, by showing the number of stargazers on Github over time for each of most popular Flux architectures in the wild.

Flux Repository Stars

For an in-depth introduction to Redux go check out the docs - it is an incredible resource, and a tribute to the dedication of the main contributor, Dan Abramov!

RethinkDB

RethinkDB is an open-source distributed database that stores JSON documents. It has been built with a strong focus on developer experience.

The key features that make RethinkDB great are:

  • embedded query language, in your language of choice
  • incredibly easy to scale
  • distributed joins
  • capability to push realtime updates, based on a query, to your application

RethinkDB's docs provide a great launchpad for a hands-on introduction to the database.

Express.js

Express is a Node.js web application framework. Its focus on minimalism allows you to build your server layer in a simple and flexible manner - perfect for this stack.

It is one of the more common frameworks used when developing a Node.js application.

The code

Talking about a stack without a code sample would be pretty lame so I've created a repo on Github containing the source code to a demo application called Pulse. It allows you to record events, and rate them. You are also able to see other people recording events too. You can see the application in action here.

Pulse Screenshot

I'll walk through some snippets and highlight key parts of the stack.

Universal JavaScript

Universal (used to be known as Isomorphic) applications are/were the holy grail of the JavaScript world. The ability to share your application logic on both server and client would drastically reduce complexity and development time of projects.

With the 3REE stack we can do this pretty easily, thanks to Redux and React. By retrieving an initialState from our service layer, creating our store and then calling React.renderToString with our main React component, we can render straight from the server!

export function handleRender(req, res) {  
  eventService.getEvents()
  .then(initialEvents => {
    // Create a new Redux store instance
    const store = createStore(rootReducer, {events: initialEvents, userId: 'baseUser'});

    // Render the component to a string
    const html = React.renderToString(
      <Provider store={store}>
        {() => <PulseApp />}
      </Provider>
    );

    // Send the rendered page back to the client
    res.render('index', { html: html, initialState: JSON.stringify(store.getState()) });
  });
}

We make initialState available to the client-side application by setting a global variable in our index template. We then reference this our client-side code to bootstrap it all up.

// Grab the state from a global injected into server-generated HTML
let initialState = window.__INITIAL_STATE__;

const loggerMiddleware = createLogger({  
  level: 'info',
  collapsed: true,
});

const createStoreWithMiddleware = applyMiddleware(  
  thunkMiddleware,
  loggerMiddleware
)(createStore);

// Create Redux store with initial state
const store = createStoreWithMiddleware(actionReducers, initialState);

React.render(  
  <Provider store={store}>
    {() => <PulseApp />}
  </Provider>,
  document.getElementById('app')
);

// Now that we have rendered...
setupRealtime(store, actions);

// lets mutate state and set UserID as key from local storage
store.dispatch(actions.setUserId(getOrSetUserId()));  

Whilst this snippet does not demonstrate routing in action, with the use of React Router, we can support server-side rendering of apps with multiple routes too! This moves us back to a world a where our applications are search engine friendly, with content accessible to web crawlers that do not load JavaScript. All whilst only maintaining one code base, with the reuse of models, routing and views.

Async

Integrating communication with a RESTful API has been one of the areas of the Flux architecture that has been an area of mystery. Documentation and concrete examples have been thin on the ground. However with Redux, the docs does a great job of explaining how to approach this using redux-thunk.

We use this approach combined with superagent, an client + server HTTP request libary to support all HTTP verbs.

By using the redux-thunk middleware we can write action creators that return functions when we want to handle asyncronous flow.

A thunk is a piece of code to perform a delayed computation (similar to a closure)

Below is an example of how we create an addEvent action, and in turn, addEventRequest and addEventSuccess or addEventFailure.

export function addEvent(event) {  
  return dispatch => {
    dispatch(addEventRequest(event));

    return request
      .post(eventsUrl)
      .send(event)
      .set('Accept', 'application/json')
      .end((err, res) => {
        if (err) {
          dispatch(addEventFailure(err, event));
        } else {
          dispatch(addEventSuccess(res.body));
        }
      });
  }
}

export function addEventRequest(event) {  
  return {
    type: types.ADD_EVENT_REQUEST,
    event
  };
}

export function addEventSuccess(event) {  
  return {
    type: types.ADD_EVENT_SUCCESS,
    event
  };
}

Whilst we introduce a side-effect in our action creators when calling request.post() we isolate this impurity to just action creators. Importantly state mutations/the reducers are kept pure. When addEvent() is called as a result of a user action from a React component, it will trigger do the following:

  • trigger an addEventRequest action
  • if the request is successful, then an addEventSuccess action is triggered
  • if the request fails, then an addEventFailure action is triggered

You can see in the source code how this action creator is passed down as the onSubmit prop in PulseApp.js to the EventInput component. There is no noticeable difference when calling the action from the view layer, you invoke the function just as you with any syncronous action creator.

Realtime

RethinkDB's changefeed API was originally launched in early 2015 to help the database integrate with other real-time systems. However, they quickly realised that they had created a solution to the broader problem of pushing real-time data to your application/client.

With the 3REE stack we can leverage this functionality to power real-time updates to an application. In our demo app we have a section devoted to 'Other Events' that updates whenever another user of the application adds an event, this is powered by RethinkDB's changefeeds.

On the server side, when our server has started we call a function, as shown below, that sets up our changes feed from RethinkDB.

export function liveUpdates(io) {  
  connect()
  .then(conn => {
    r
    .table('pulses')
    .changes().run(conn, (err, cursor) => {
      cursor.each((err, change) => {
        io.emit('event-change', change);
      });
    });
  });
}

Whenever a change is detected we emit a message using Socket.io to each of our connected clients.

You may recall from an earlier snippet the function call setupRealtime(store, actions). This sets up Socket.io on our client side application that listens for any changes that are pushed to the clients.

export function setupRealtime(store, actions) {  
  const io = socketClient();

  io.on('event-change', (change) => {
    let state = store.getState();
    if (!change.old_val) {
      if (change.new_val.userId !== state.userId) {
        store.dispatch(actions.addEventSuccess(change.new_val));
      }
    }
  });

  return io;
}

In this snippet whenever an event-change message is received we inspect the object in the message to see if it is for a new event being added., if it is and it is not owned by the current user, then we dispatch an addEventSuccess action. This results in the UI updating and showing a new event has been added by another user.

Summary

We've gone through a quick overview of the key aspects of the 3REE stack and what can be acheived when bringing all these technologies together.

Myself and the team at Workshape.io are very excited by the potential of this stack. Even if you choose not to use all the ingredients I think it serves as a good introduction to how you can go about building a Universal Javascript application. I hope you have enjoyed this blog.

N.B If you'd like to contribute to improving the repository, please get in contact, post an issue or make a Pull Request!