Creating a Monolithic Rails + React/Redux Application

The source code for this tutorial can be found here.

It seems like React has taken the world by storm. With a new Javascript framework coming out what seems like every day, React feels different. It feels like it’s here to stay for a while. Between the immense reusability that it’s component system provides and the ability for use on every platform (web, iOS, Android) I think that React is going to stick around for the foreseeable future.

Traditionally when you want to use a front-end framework you reach for a backend server that also uses Javascript such as Express or Happi. Depending on the project, these can be great choices and many times are the right choice. However if you’re unfamiliar with Javascript backends, trying to work quickly, or just don’t need the features that these frameworks provide it may be worthwhile to use Ruby on Rails as your backend!

The big issue is that Rails doesn’t really play nice with Javascript. It treats Javascript as a second-class citizen that is only there because people have to have it. It’s solved asset compression through the Asset Pipeline which “provides a framework to concatenate and minify or compress JavaScript and CSS assets”. Now, the Asset Pipeline is fine for a couple of jQuery calls here and there but there’s no dependency management system which makes working with a front-end framework near impossible.

Creating a Rails App That Uses NPM for Javascript Instead of the Asset Pipeline

Prep for Heroku

We’re going to be creating an app that’s ultimately going to be hosted on Heroku. Because of this, we’ll make use of a PostgreSQL database.

$ rails new test_app --database=postgresql
$ cd test_app/
$ rake db:create

Since the app’s going on Heroku we’ll need to add the following to our Gemfile.

gem 'rails_12factor', group: :production

We also need to remove the coffee-rails, jquery-rails, and turbolinks gems. Run a quick bundle install and we’re all prepped.

Remove Rails Generated Javascript Configuration

At this point we can remove our app/assets/javascripts/application.js file since we won’t be writing our Javascript here. After doing this we’ll need to update our app/views/layouts/application.html.erb’s head section removing turbolinks and it’s reference to the application.js file we just deleted. It should look like this:

<title>TestApp</title>
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= csrf_meta_tags %>

We’ll also add <%= javascript_include_tag 'bundle' %> above the closing body tag. Later, when we setup Webpack, we’ll designate compilation to happen to that bundle file. One more thing that we need to do is to setup that bundle file to go through rails precompilation making it end up in your public folder during Heroku’s build process. We can do that by adding the following to config/initializers/assets.rb:

Rails.application.config.assets.precompile += %w( bundle.js )

Now we want to remove all asset generation that comes by default in rails generators. We can do this by adding these two lines in config/application.rb file.

config.generators.stylesheets = false
config.generators.javascripts = false

Before adding NPM we’re going to setup a controller and view for our React app to mount to.

$ rails g controller dashboard index

Add the following to your config/routes.rb file:

root 'dashboard#index'

In your app/views/dashboard/index.html.erb file add:

<div id="root"></div>

Adding NPM

You can either run an npm init at this stage or just copy and paste the following package.json file into your root directory.

{
	"name": "test_app",
	"private": true,
	"dependencies": {
		"babel-core": "^6.26.0",
		"babel-loader": "^7.1.2",
		"babel-preset-es2015": "^6.24.1",
		"babel-preset-react": "^6.24.1",
		"babel-preset-stage-0": "^6.24.1",
		"react": "^16.2.0",
		"react-dom": "^16.2.0",
		"react-redux": "^5.0.6",
		"redux": "^3.7.2",
		"redux-logger": "^3.0.6",
		"webpack": "^3.10.0"
	},
	"description": "A test app.",
	"version": "1.0.0",
	"scripts": {
		"postinstall": "webpack --config webpack.config.prod.js"
	},
	"license": "MIT"
}

This file holds all the dependencies that you’ll need to run a basic React application using Redux. Run an npm install to install all dependencies to your node_modules/ folder.

Setting Up Our React App

For the sake of this tutorial, I’m going to assume that you have basic React knowledge and am going to go pretty quickly through this section. It’s just setting up a standard React + Redux application and doesn’t have much to do with it’s configuration within Rails.

Within your app/ folder, create a folder named frontend/ and within that folder create one called javascripts/ keeping with Rails conventions. Within that folder create a file main.js. It should look like this:

// app/assets/frontend/javascripts/main.js

import React from 'react';
import { render } from 'react-dom';
import App from './components/App';
import configureStore from './redux/store';
import { Provider } from 'react-redux';

let initialState = {};
let store = configureStore(initialState);

render(
<Provider store={store}>
<App/>
</Provider>,
document.getElementById('root')
);

In the same directory as this file create another folder called components/ and within it create an App.js file. It should look like this:

// app/frontend/javascripts/components/App.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import actions from '../redux/actions';

class App extends Component {
render() {
return (
<div>
<p>This is A Boilerplate React and Redux App :)</p>
</div>
);
}
}

function mapStateToProps(state) {
return {
state
};
}

function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}

export default connect(mapStateToProps, mapDispatchToProps)(App);

In the same directory you created the components/ folder create another one called redux/. This folder should contain three more folders; actions/, reducers/, and store/.

Within the actions/ folder, create an index.js file that looks like this:

// app/frontend/javascripts/redux/actions/index.js

let actions = {
exampleAction: () => {
return {
type: 'EXAMPLE_ACTION'
};
}
};

export default actions;

Within the reducers/ folder, create an index.js file that looks like this:

// app/frontend/javascripts/redux/reducers/index.js

import { combineReducers } from 'redux';

let reducer = (state = {}, action) => {
switch (action.type) {
default:
return state;
}
};

const rootReducer = combineReducers({
reducer
});

export default rootReducer;

Within the store/ folder, create an index.js file that looks like this:

// app/frontend/javascripts/redux/store/index.js

import { applyMiddleware, compose, createStore } from 'redux';
import reducer from '../reducers';
import { createLogger } from 'redux-logger';

let finalCreateStore = compose(
applyMiddleware(
createLogger()
)
)(createStore);

export default function configureStore(initialState = {}) {
return finalCreateStore(reducer, initialState);
}

Integrating Our React App with Rails

We’re almost there! The last piece of the puzzle is to setup a build tool that’ll compile the Javascript in our app/frontend/ folder into the bundle.js file that’s being used by Rails. We’re going to use Webpack though you could use a variety of tools such as Grunt or Gulp.

Before we dive into the Webpack files themselves, we’ll need to create a .babelrc file in our main directory. This will let Webpack know that we’re using JSX in our React files and also to look out for ES2015 Javascript. Ours will look like this:

{
"presets": ["react", "es2015", "stage-0"]
}

Create a file called webpack.config.dev.js in your root directory. It should look like this:

var path = require('path');
var webpack = require('webpack');

module.exports = {
devtool: 'eval-source-map',
context: __dirname,
entry: ['./app/frontend/javascripts/main.js'],
output: {
path: path.join(__dirname, 'app', 'assets', 'javascripts'),
filename: 'bundle.js',
publicPath: '/assets'
},
resolve: {
extensions: ['.js']
},
plugins: [
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.NoEmitOnErrorsPlugin()
],
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
}
};

At this point if we run rails s in one Terminal window and webpack --config webpack.config.dev.js --watch --colors in another Terminal window we shouldn’t receive any errors. If we fire up our browser and go to localhost:3000 we should see the contents of our React app on the page!

Deploying to Heroku

We’re close! Initialize a git repository and put the following in your .gitignore.

# Ignore bundler config.
/.bundle

# Ignore all logfiles and tempfiles.
/log/*
!/log/.keep
/tmp

# Ignore application configuration
config/database.yml

# Ignore automatically generated files from OSX
.DS_Store

# Ignore node_modules
node_modules/

# Ignore Webpack Generated Bundle
app/assets/javascripts/

Commit your changes:

$ git add –-all
$ git commit -m "my first rails and react app"

Next you’ll need to create an empty Heroku app.

$ heroku create test-app-956

Since we’re using both Rails and Node we’ll need to re-configure the default buildpacks.

$ heroku buildpacks:clear
$ heroku buildpacks:set heroku/nodejs
$ heroku buildpacks:add heroku/ruby --index 2

The --index 2 tells Heroku to add the Ruby buildpack after the Node one. This is important since Webpack will need to build/bundle our Javascript before Rails precompiles our bundle.

We need to do a little bit of configuration in our app before we deploy. The first thing we need to do is to setup an additional Webpack file that’s made for production. Create a file in your root directory called webpack.config.prod.js that looks like this:

var webpack = require('webpack');
var path = require('path');

module.exports = {
context: __dirname,
entry: ['./app/frontend/javascripts/main.js'],
output: {
path: path.join(__dirname, 'app', 'assets', 'javascripts'),
filename: 'bundle.js',
publicPath: '/assets'
},
resolve: {
extensions: ['.js']
},
plugins: [
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false
}
})
],
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
}
};

We’ll also need to add a line to our package.json in the scripts section:

"postinstall": "webpack --config webpack.config.prod.js"

This tells Heroku to run Webpack after installing all node modules.

Commit your changes and then push to Heroku:

$ git push heroku master
$ heroku run rake db:migrate
$ heroku open

Congratulations, you’ve successfully created an application using Rails, React, and Redux!

The source code for this tutorial can be found here.

Need help with a project? We'd love to hop on board. Hire Us.

Get In
Touch

Must be a valid email address.