Clicky

Building a Stateless Rails API with React and Twitter OAuth

logos

I recently built a demo project in order to learn a few new libraries, new to me anyway, those being React, React-Router, Webpack, and Rails-Api, which seemed like a good chance to do some writing and maybe spare someone a little pain in picking these things up for themselves. I'm going to assume some basic familiarity with Rails because there is a ton of ground to cover, but issues related to integrating the Rails API with the client are covered in detail. Even if you don't know or care about Rails, much of this post should still be usable because the React client is completely decoupled from the API and could just as easily live in it's own repo and be deployed as a separate application.

TLDR: The end result will be a Heroku deployed, JSON only server based on the Rails-Api gem to exclude action-view, sprockets, and sessions, using Twitter OAuth for identity and JSON Web Tokens for authentication, and a client based on React, React-Router, and Webpack. Here is the finished code, and here is a live demo.

Why This Architecture?

Part of the reason these libraries are interesting is the role they can play in the generalized architecture of stateless servers with JavaScript heavy clients, as opposed to HTML generating servers that are coupled to the browser by their own sessions. The strongest arguments for this architecture are better user experience, since changes to the interface can be rendered without a round trip to the server, and the ability to take an API originally built for a browser client and easily reuse it for iOS and Android clients. Even though this idea has been around for a few years, it's still new relative to stateful, HTML generating servers, and with fewer accepted best practices, let alone pre-baked solutions, which can make it a challenge getting started if you're accustomed to using Rails, or an alternative batteries-included framework. Another option would be a Firebase backend with 100% of the application code in the client, but if you need to run background processes, interact with services from the backend, or have sufficiently complex logic tied to your models, you will want a proper server you can fully control.

Why Use React?

React, which is advertised simply as "a JavaScript library for building user interfaces" as opposed to an MVC framework, takes a fundamentally different view of dynamic rendering in the browser than any established solution that preceded it. Tom Occhino, a PM on the team led by Jordan Walke that created the technology behind React, describes the conceptual model that React aspires to as, anytime something occurs that would require a change to the DOM, just "blow it away and re-render" rather than mutate the existing DOM. What makes the re-render concept not enormously wasteful and slow, and in fact much faster than Angular or Ember, is an in memory representation of the DOM with a diffing and batching algorithm that another React team member, Pete Hunt, talks about here, and really that whole presentation is worth watching in terms of understanding the philosophical underpinnings of React. What all of this allows you to do is write simple, declarative components that describe your interface once, rather than imperative mutations to move between UI states. This is good. When virtually every competing technology in a given space starts to copy one solution, it's probably a good solution.

A Little Housekeeping

If you want to follow along with the code you'll need to create a Twitter app through their dev site in order to get API keys for Twitter Oauth. Once that's done you'll need to install Ruby 2.2.0, Node 0.10.x, and Postgres locally if you haven't done so in the past. If you're following along on Windows hopefully you already know how to install things and run bash commands on Windows.

Getting Started

Ok let's build the demo project, a simple app that allows users to sign in with Twitter and write posts on a wall. To clone the starter repo, open Terminal and run:

git clone https://github.com/fredguest/rails-api-react-demo-starter.git blabber

If you look at top level directory that was just created, and if you're familiar with Rails, you'll notice a few big differences right away. Inside the "app" folder, the "views", "helpers", "mailers", and "assets" folders are all missing and will stay that way. Another notable difference is inside the "config/application.rb" file, where the requires look like this:

# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "action_controller/railtie"
# require "action_mailer/railtie"
# require "action_view/railtie"
# require "sprockets/railtie"
# require "rails/test_unit/railtie"

So as you can see we're not even including any of the modules related to rendering HTML or serving assets, because we won't use them, but also because that will speed up response times. So far, this is almost exactly what you would get from simply installing the Rails-Api gem and running "rails-api new myapp", but we're starting with this repo instead to save a bunch of folder and file creation, some copying and pasting (mostly CSS), and some config setting (mostly puma.rb).

In addition to the things that are missing, you'll notice there is a folder called "client", which as you might imagine is where the React code will live. Ignore that whole directory tree for now, it's just there to save time later and contains no code except for some CSS that's not even being served yet.

To finish setting up, go back to Terminal where you just cloned the repo and run:

cd blabber

and then rename the example dev environment file by running:

mv config/environments/development.example.rb config/environments/development.rb

Then paste your Twitter app's consumer key inside the empty string on line 31 of "config/environments/development.rb", and paste your Twitter app's consumer secret inside the empty string on line 32 of "config/environments/development.rb" which will enable you to use Twitter OAuth. Next let's finish installing Rails by running the following commands, one at a time, in Terminal:

gem install bundler --no-ri --no-rdoc
gem install rails -v 4.2.0 --no-ri --no-rdoc
bundle install
rake db:create && rake db:migrate

At this point you should be ready to start the server so test that by running:

rails server

and then visiting http://localhost:3000 in your browser. If you see a blank page that just says "Blabber" then everything is working. If you don't then double check that you haven't skipped any steps, including the stuff in the "housekeeping" section.

The First JSON Endpoint

Lets set up the first JSON endpoint, which will just return an array of blabs. First we'll create a table for the blab model. Open Terminal and run:

rails g migration create_blabs

and then modify the file "db/migrate/{timestamp}_create_blabs" as follows:

class CreateBlabs < ActiveRecord::Migration
  def change
    create_table :blabs do |t|
      t.string :content, null: false
      t.timestamps
    end
  end
end

and finally, back in Terminal, migrate the database:

rake db:migrate

Now add a simple validation for the presence of content in "app/models/blab.rb"

class Blab < ActiveRecord::Base
  validates_presence_of :content
end

a single resourceful route for the index action in "config/routes.rb"

Rails.application.routes.draw do
  resources :blabs, only: [:index]
end

and an index action in the controller "app/controllers/blabs_controller.rb"

class BlabsController < ApplicationController
  def index
    @blabs = Blab.all.order(created_at: :desc)
    render json: @blabs
  end
end

The only thing we just did differently from a standard Rails app is in the controller where we're only ever returning JSON so we don't need a respond_to block. To double check that everything is working, let's go back to Terminal and start up the console so we can create a few records in the database. Start the console with:

rails console

and then create two quick blabs by running:

Blab.create([{content: 'this is a blab'}, {content: 'and another blab'}])

and then exit the console and start up the server again. At this point if you visit http://localhost:3000/blabs in your browser, you should just see an array of JSON objects with the two records we created:

[
  {
    "id": 2,
    "content": "and another blab",
    "created_at": "2015-03-05T20:29:44.575Z",
    "updated_at": "2015-03-05T20:29:44.575Z"
  },
  {
    "id": 1,
    "content": "this is a blab",
    "created_at": "2015-03-05T20:29:44.559Z",
    "updated_at": "2015-03-05T20:29:44.559Z"
  }
]

You should see the last record created as the first object in the array, as we specificed by the sort order in the controller. This now constitutes the bare minimum that could be described as a JSON API using the Rails-Api gem, so let's get straight into figuring out how to render this with a React client.


★ Here is a progress checkpoint for "The First JSON Endpoint".


A Brief Introduction to Webpack

The first problem to be solved after removing the asset pipeline from Rails is how to intelligently transform and deliver the assets with which we will construct the client. At a minimum, a React application will depend on the React library itself, an HTTP library so that React can talk to the server, and the JavaScript you write on top of that, but in most cases you will integrate additional libraries. In this case we'll also be using React's JSX Transformer, the React-Router package, and a URL parser. In order to address this properly, you really need to explore the ecosystem of Node build tools, which in my case was totally foreign. The good news is it's a vibrant ecosystem with many options, which of course is also the bad news because you have to sort through it all, potentially without any context. We're using Webpack here because, once again, it appears to represent thought leadership in it's space.

This is not going to be a master class on Webpack, which can do cool things like handle multiple entry points for your application, code splitting so only the precise subset of the total assets required for any particular view are delivered to the browser, and hot module replacement for development speed. Those features are definitely worth exploring, maybe in another post, but for now we're just using a very basic setup so we can get to React.

Having installed Node, you'll have access to NPM, which is analogous to Bundler in Ruby land, so that's how we'll install all things JavaScript. As Bundler has a Gemfile, NPM uses a file called "package.json" to manage dependencies, so step one of using NPM is to create that file. Just to dispel any potential confusion, when we eventually deploy to production on Heroku, the server will only have a single runtime, and that will be Ruby. The application won't have a Node runtime, but Heroku allows you to employ multiple runtimes in sequence during the build process, and that's when we will use Node. So we're not building a Node app, we're just using Node to build a React client, if that makes sense.

Create a file called "package.json" at the root of the application where the Gemfile is, it must be at the root for Heroku to find it, and paste the following into it:

{
  "name": "Blabber",
  "version": "0.0.1",
  "description": "an app for blabbing",
  "main": "client/main.jsx",
  "repository": {
    "type": "git",
    "url": ""
  },
  "scripts": {
    "devserve": "webpack-dev-server -d --config webpack.config.js --content-base public/ --progress --colors",
    "devbuild": "webpack -d --config webpack.config.js --profile --progress --colors"
  },
  "author": "Your Name",
  "license": "ISC",
  "engines": {
    "node": "0.10.x"
  },
  "dependencies": {
    "css-loader": "^0.9.1",
    "jsuri": "^1.3.0",
    "jsx-loader": "^0.12.2",
    "react": "^0.12.2",
    "react-router": "^0.12.4",
    "reqwest": "^1.1.5",
    "style-loader": "^0.8.3",
    "webpack": "^1.6.0",
    "webpack-dev-server": "^1.8.0"
  }
}

We've seen all of the dependencies listed above except for the loaders, which are the packages that Webpack uses to transform assets, Reqwest for lightweight AJAX, and jsUri for parsing URLs. To install everything go to Terminal and run:

npm install

You now have a directory in the application called "node_modules", which contains all of the JavaScript packages that NPM installed for you. Another thing to take note of in "package.json" is the "scripts" field, in which you the can include any code you like that is runnable by Node, and name the keys anything you like. In this case we have two scripts, which we can call from Terminal by running "npm run devserve" or "npm run devbuild". The devserve script will start Webpack's dev-server which will serve the assets that make up the React app as we are writing code, and will rebuild them anytime we save a change to a file that Webpack is responsible for. The devbuild script is not actually necessary, but I'll explain it's value later.

In order for Webpack to function, it also requires a configuration file. Create another file at the root, this time called "webpack.config.js", which you can see our devserve and devbuild scripts will reference, and paste in the following:

module.exports = {
  entry: ['./client/main.jsx'],
  output: {
    path: './public',
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      { test: /\.css$/, loader: 'style!css' },
      { test: /\.jsx$/, loader: 'jsx-loader' }
    ]
  }
};

What we're doing in this file is telling Webpack which loaders to apply to different files based on their extensions, in this case files that end in ".css" or ".jsx". We're also defining a single entry point for Webpack to watch, and telling Webpack to output what it builds to a file called bundle.js in the public directory.

The First React Component

Now that we have a simple Webpack setup, let's give it some application code to manage. Open "client/components/layout/App.jsx" and enter the following:

var React = require('react');

module.exports = React.createClass({
  render: function () {
    return (
      <h1>Hello world!</h1>
    );
  }
});

This is React's version of "hello world". We require React and then call React.createClass() to build a component, which simply takes a JavaScript object as an argument. The only thing that object must contain to build a valid React Class is a "render" property, which calls an anonymous function to return some markup, or what appears to be markup. You would normally expect to see that h1 tag inside a string for it to be handled by JavaScript, but it's not actually HTML, it's JSX, which the JSX loader in Webpack will convert to JavaScript. JSX allows you to write markup inside of JavaScript without horrible string concatenation everywhere.

We're almost ready to render, but remember Webpack doesn't know this file exists, it's only aware of main.jsx, so we have to perform the ultimate render from there. Open up "client/main.jsx" and enter the following:

require('./assets/app.css');
require('./assets/menu.css');
require('./assets/blabs.css');

var React = require('react');
var App = require('./components/layout/App.jsx');

React.render(<App/>, document.body);

The first three lines just pass our custom CSS to Webpack, and this could instead be Sass, Less or whatever you like, just add the appropriate loader. Next we require React, and the component we just wrote. In case you're unfamiliar with "module.exports", which is everywhere in React, basically module.exports is to a file what "return" is to a function, so when we create a variable called "App" and assign it the value of "require('./components/layout/App.jsx')", it is being assigned the value of "module.exports" inside of App.jsx. Also, this directory structure seemed sensible to me, but as long as the file paths in your requires point to the right places, you can name and structure your files and folders however you like, React doesn't care. Capitalizing component file and variable names is a convention though.

In the last line we call React.render() that, from the docs, "instantiates the root component (the first argument), starts the framework, and injects the markup into a raw DOM element (the second argument)." Components are referenced in JSX as capitalized tags, as you can see with the "<App/>" tag.

Finally, we need to include the bundle.js file that Webpack will build, so open up "public/index.html", delete "<h1>Blabber</h1>", and enter the following:

<body>
  <script src="bundle.js"></script>
</body>

If you build your entire frontend in React, which we will, this can constitute the entire static DOM, which is kind of neat. Now if you open Terminal and run:

npm run devserve

and visit http://localhost:8080 which is where Webpack's dev-server points by default, you should see the "Hello world!" component rendered. If you look in the public directory right now, you won't actually see a file called "bundle.js", because it is being generated dynamically by Webpack's dev-server, but if you were to stop the dev-server and run "npm run devbuild", you would then see a static build of bundle.js in the public directory.

For the sake of understanding how the production environment will work, you can run "npm run devbuild" and then instead of starting Webpack's dev-server, run "rails server" and visit http://localhost:3000 again instead of http://localhost:8080. When we deploy to production, we will give Heroku a similar script to create a static build of bundle.js that will live in the public directory, and then start up Rails to serve the JSON endpoints that React will consume from index.html. After trying that, you can delete the bundle.js file that was created because we won't use it.


★ Here is a progress checkpoint for "The First React Component".


Rendering Collections

Even though we're simply going to render a list of elements, it's not too early to start thinking about how to break the interface up into modular components. This view will follow the same structure as the comment box in the official tutorial, so the component structure will be:

- BlabView
  - BlabList
    - Blab
  - BlabForm

Building from the outside in, open up "client/components/blabs/View.jsx" and enter:

var React = require('react');
var BlabsList = require('./List.jsx');

module.exports = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.setState({data: [{id: 2, content: 'and another fake blab'}, {id: 1, content: 'this is a fake blab'}]});
  },
  render: function() {
    return (
      <div className="blabs-view">
        <BlabsList data={this.state.data} />
      </div>
    );
  }
});

This code introduces several new ideas. Starting from the top, we are requiring a file called List.jsx that doesn't exist yet, but will contain the BlabsList component. This is a pattern we'll follow throughout, with parent components requiring their child components and then returning them in the render function. As you can see, the current component, BlabsView, ultimately renders another component called BlabsList. You can also see that BlabsList is being given a property called "data", with a value of "this.state.data", which is how data is passed from one component to another. React encourages a unidirectional data flow, so we will only ever pass data downwards from parents to children, never upwards fom children to parents, or horizontally across siblings. Apparently the current component has something called "state" in it's scope, and state has an attribute called "data". In this case "data" is an attribute that we named and assigned to the current scope's state in the getInitialState() function, which is one of several hooks that React gives you into the lifecycle of a component, somewhat analogous to ActiveRecord callbacks in Rails.

So what is "state"? A React component is literally just a function of it's state and it's props. State should contain dynamic values that impact the UI, and a component is responsible for managing it's own state. Props are usually received from upstream and may contain data, configuration, or functionality, and they are immutable as far as the component receiving them is concerned. In this case, we have added "data" to BlabView's state, and subsequently assigned the value of that state to a prop of BlabsList, so we have restricted the responsibilty for mutating that piece of state to BlabView, but BlabList will still receive any changes in that state as this.props.data. This pattern is described in the docs:

A common pattern is to create several stateless components that just render data, and have a stateful component above them in the hierarchy that passes its state to its children via props. The stateful component encapsulates all of the interaction logic, while the stateless components take care of rendering data declaratively.

Finally, in componentDidMount(), one of the lifecycle hooks mentioned earlier, we update the state we first set in getInitialState() by mocking the data we'll eventually fetch from our API. We update state by calling this.setState(), which is kind of the heart of React. The setState() function is where the magic happens, passing it an object that represents some piece of state will trigger a re-render, if the the new state is different than the old state, but not to the actual DOM, to the Virtual DOM, which will automatically compute the minimum possible set of DOM mutations based on the entire dependency tree of that state, batch them, and execute them for you. So in this case calling setState() in BlabsView will apply any necessary mutations to BlabsList, because we passed a piece of BlabsView's state to BlabsList as a prop. The reason we give "data" an empty array in getInitialState() is to maintain the contract with BlabsList, which expects a prop called "data" to contain an array for it to iterate through, and we don't want the initial render to have to wait for a completed round trip to the server.

Next create the file "client/components/blabs/List.jsx" and enter:

var React = require('react');
var Blab = require('./Blab.jsx');

module.exports = React.createClass({
  render: function() {
    var blabs = this.props.data.map(function(blab) {
      return (
        <Blab key={blab.id} content={blab.content} />
      );
    });

    return (
      <ul className="blabs-list">
        {blabs}
      </ul>
    );
  }
});

Now in BlabsList, we iterate through this.props.data using JavaScript Array's built in map() function, and return a Blab component for each element in the array. Inside of map(), BlabsList passes a "key" prop and "content" prop to each Blab. In the content prop, we assign the value of the attribute "content" from the record we fetched from the database, and the key prop get's that records id. React requires a prop called key with a unique value whenever rendering a collection, so that it can keep track of which items in the collection may require mutation. We then include this variable "blabs" using curly braces in the final return value of BlabsList's render function.

To build each Blab, create the file "client/components/blabs/Blab.jsx" and enter:

var React = require('react');

module.exports = React.createClass({
  render: function() {
    return (
      <li className="blab">
        <span className="blab-text">{this.props.content}</span>
      </li>
    );
  }
});

This component is extremely straightforward, it just renders a single list item using it's "content" prop, accessed through curly braces, as the inner HTML.

The last step is to modify "client/components/layout/App.jsx" as follows:

var React = require('react');
var BlabsView = require('../blabs/View.jsx');

module.exports = React.createClass({
  render: function () {
    return (
      <div id="content">
        <BlabsView/>
      </div>
    );
  }
});

So now the parent component of the view we just built is being required and rendered in the root component of the app. Now it's time to open up Terminal, run "npm run devserve", and visit http://localhost:8080 to bask in programming glory. If you want to see React's magic setState() function in action, inside the BlabsList component, add a line directly below the "render: function() {" line, and paste "console.log('Rendered!', this.props.data);" and after saving and refreshing the browser, you should see two lines logged in the console, one with an empty array and one with the mock data.


★ Here is a progress checkpoint for "Rendering Collections".


Communicating With The API

Now things get a little tricky. In production, the React client will be served from the same origin as the API, so requests from the client to the API will be normal, same origin requests. However, in development, because we want to be able to continually change the React files without manually running "npm run devbuild" every time we change something, we are using the Webpack dev-server, which serves the React files from a different origin than the API is served from. In development, the API is served from http://localhost:3000 but React is served from http://localhost:8080, so we will be making cross origin requests. Unfortunately we can't just tell the browser to chill out when we're on localhost, it will jam us up anyway, so our application has to be smarter than the browser.

To implement this, open up "app/controllers/application_controller.rb" and enter:

class ApplicationController < ActionController::API
  before_action :allow_cross_origin_requests, if: proc { Rails.env.development? }

  def preflight
    render nothing: true
  end

private
  def allow_cross_origin_requests
    headers['Access-Control-Allow-Origin'] = '*'
    headers['Access-Control-Request-Method'] = '*'
    headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, OPTIONS'
    headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
    headers['Access-Control-Max-Age'] = '1728000'
  end
end

So we now have a private method that will execute before every single request to the API and set the headers to allow requests from any origin, but we don't want this method to run in production at all, so we have restricted it to the development environment only. We've also added an action called "preflight" that renders nothing. I always thought there were only four HTTP request methods, GET, PUT, POST, and DELETE, but it turns out there a few others, one of which is called OPTIONS. When making a cross origin request, the HTTP client will automatically make an additional request called a "preflight check" before the actual request you are expecting it to make. The preflight check uses the OPTIONS request method, basically just to ask the server for a green light to make subsequent cross origin requests, and it doesn't actually require a response body, just the headers to tell it what's allowed. We need a route to handle these requests, so modify "config/routes" as follows:

Rails.application.routes.draw do
  match '*all', to: 'application#preflight', via: [:options]
  resources :blabs, only: [:index]
end

This new route will catch any request made with the OPTIONS method and direct it to the preflight action, which, in the development environment, will have headers that allow cross origin requests. At this point we can start making requests from React to the API, so let's do that. We're going to put the request logic in the root component so it can be accessed from any component we pass it to without rewriting the logic. Open up App.jsx and enter:

var React = require('react');
var Reqwest = require('reqwest');
var BlabsView = require('../blabs/View.jsx');

module.exports = React.createClass({
  getDefaultProps: function() {
    return {origin: process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : ''};
  },
  readFromAPI: function(url, successFunction) {
    Reqwest({
      url: url,
      type: 'json',
      method: 'get',
      contentType: 'application/json',
      success: successFunction,
      error: function(error) {
        console.error(url, error['response']);
        location = '/';
      }
    });
  },
  render: function () {
    return (
      <div id="content">
        <BlabsView origin={this.props.origin} readFromAPI={this.readFromAPI} />
      </div>
    );
  }
});

We've designed the readFromAPI() function to take two arguments, the URL of the request, and a successFunction to execute upon completion of a successful request, so that it is flexible enough for any component to make use of. We then pass this function to BlabsView as a prop, along with another prop called "origin". In production, we can use relative paths for API requests, again because React and the API will have the same origin, but in development if we request "/blabs", it will go to "http://localhost:8080/blabs" because that's React's origin, but we want it to go to "http://localhost:3000/blabs", so we need to set the request origin based on the environment. We self assign the origin prop in getDefaultProps(), which is another built in hook like getInitialState(). But how can we access NODE_ENV if we don't have a Node runtime? We can't, but we can manually set that value in Webpack during the build. Open "webpack.config.js" and add a field to push plugins onto:

module.exports = {
  entry: // unchanged
  output: {
    // unchanged
  },
  module: {
    // unchanged
  },
  plugins: []
};

Then create a new file called "webpack.dev.config.js" and enter:

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

config.plugins.push(
  new webpack.DefinePlugin({
    "process.env": {
      "NODE_ENV": JSON.stringify("development")
    }
  })
);

module.exports = config;

and another new file called "webpack.pro.config.js" containing:

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

config.plugins.push(
  new webpack.DefinePlugin({
    "process.env": {
      "NODE_ENV": JSON.stringify("production")
    }
  })
);

config.plugins.push(
  new webpack.optimize.UglifyJsPlugin({
    compress: {
      warnings: false
    }
  })
);

module.exports = config;

It looks like a lot of code, but all we're doing is using the DefinePlugin to set NODE_ENV in development and production, and also quieting some unnecessary UglifyJs warnings that would otherwise add noise to the production build logs.

Now we just need to point the "package.json" dev scripts to the dev configs:

"scripts": {
  "devserve": "webpack-dev-server -d --config webpack.dev.config.js --content-base public/ --progress --colors",
  "devbuild": "webpack -d --config webpack.dev.config.js --profile --progress --colors"
},

Finally, we can now make an API request. Open "client/components/blabs/View.jsx", remove the mock data inside of componentDidMount() and replace it with a call to readBlabsFromAPI(), inside of which we'll make an API request as follows:

// unchanged requires

module.exports = React.createClass({
  getInitialState: function() {
    // unchanged
  },
  componentDidMount: function() {
    this.readBlabsFromAPI();
  },
  readBlabsFromAPI: function() {
    this.props.readFromAPI(this.props.origin + '/blabs', function(blabs) {
      this.setState({data: blabs});
    }.bind(this));
  },
  render: function() {
    // unchanged
  }
});

So the call itself is pretty straightforward, we're just passing the URL we want to hit, and the success function, which just calls setState() with the data it get's back from the API. The only thing remotely tricky is that we call ".bind(this)" in order to maintain the original context of "this" inside the success function.

From now on, you will need two tabs open in Terminal, one to run the Rails server for the API, and one to run the Webpack dev-server for React. After running "rails server" in one tab, and "npm run devserve" in the other, if you visit http://localhost:8080 you should see the records we created in the database instead of the mock data.


★ Here is a progress checkpoint for "Communicating With The API".


Adding Routes to React

Since we're only relying on Rails for JSON, we're not actually using Rails routes to navigate between pages, so we need to emulate page routes in React if we're going to have any kind of navigation between views. As mentioned, we'll use the excellent React-Router library, but first we need more than one view to navigate between. Let's just make a simple "about" page in order to necessitate navigation. Open up the file "client/components/static/AboutView.jsx" and enter:

var React = require('react');

module.exports = React.createClass({
  render: function() {
    return (
      <div id="about-view">
        <h1>About</h1>
        <p>An example of a stateless Ruby API using the rails-api gem, with a React client side app. To fit the definition of stateless, the API does not include action-view, sprockets, or sessions. Roughly speaking, React replaces action-view, Webpack replaces sprockets, and JWT replaces sessions.</p>
      </div>
    );
  }
});

There's nothing in AboutView that we haven't already seen, so let's move ahead to making navigation. Create the file "client/components/layout/Menu.jsx" and enter:

var React = require('react');
var Router = require('react-router');
var Link = Router.Link;

module.exports = React.createClass({
  render: function() {
    return (
      <div id="menu">
        <span id="menu-link" onClick={this.props.sendMenuClick}><span></span></span>
        <div id="menu-list">
          <div className="pure-menu pure-menu-open">
            <span className="pure-menu-heading">Blabber</span>
            <ul>
              <li><Link to="blabs">Blabs</Link></li>
              <li><Link to="about">About</Link></li>
            </ul>
          </div>
        </div>
      </div>
    );
  }
});

In the navigation markup, we use Link to render anchor tags that are owned by React-Router and automatically receive an active class when the path they link to is active. We also put a click handler on "menu-link" that will hide and show the menu on smaller screens, and that handler in turns fires an upstream handler that we'll create on the root component, because we need a click on the menu link to mutate an upstream element. We're relying on Pure CSS, which is linked to in index.html, hence the class names that you see here.

In "client/config/routes.jsx", define the structure of the Routes as follows:

var React = require('react');
var Router = require('react-router');
var App = require('../components/layout/App.jsx');
var BlabsView = require('../components/blabs/View.jsx');
var AboutView = require('../components/static/AboutView.jsx');
var DefaultRoute = Router.DefaultRoute;
var Route = Router.Route;

module.exports = (
  <Route name="app" path="/" handler={App}>
    <DefaultRoute name="blabs" handler={BlabsView} />
    <Route name="about" handler={AboutView} />
  </Route>
);

The root component App is assigned to the outermost Route, which is active at "/". AboutView is assigned to a Route that is active at "/about". BlabsView is assigned to a Route that is active at "/blabs", but we've made it the DefaultRoute, so it's parent's Route takes precedence, making it active at "/". Since displaying blabs is the primary function of the app, they should render as soon as a user visits the root path.

Render the output of the routes we just defined by modifying App.jsx as follows:

// unchanged other requires
var Menu = require('./Menu.jsx');
var Router = require('react-router');
var RouteHandler = Router.RouteHandler;

module.exports = React.createClass({
  getDefaultProps: function() {
    // unchanged
  },
  getInitialState: function() {
    return {showMenu: false};
  },
  handleMenuClick: function() {
    this.setState({showMenu: !this.state.showMenu});
  },
  readFromAPI: function(url, successFunction) {
    // unchanged
  },
  render: function () {
    var menu = this.state.showMenu ? 'show-menu' : 'hide-menu';

    return (
      <div id="app" className={menu}>
        <Menu sendMenuClick={this.handleMenuClick} />
        <div id="content">
          <RouteHandler origin={this.props.origin} readFromAPI={this.readFromAPI} />
        </div>
      </div>
    );
  }
});

Instead of explicitly rendering BlabsView and AccountView, we are rendering a component owned by React-Router called RouteHandler that manages which view to render for us. Outside of the "content" div, we are explicitly rendering Menu, because it should always be rendered regardless of which view is also rendered. We've also we've given App some state called "showMenu", which gets toggled in handleMenuClick(), which as we saw previously will be triggered by a click event on "menu-link" in the Menu component.

Finally we need to call run() with the routes we created in "client/config/routes.jsx", which we haven't actually referenced anywhere yet. In "client/main.jsx" enter:

// unchanged CSS requires

var React = require('react');
var Router = require('react-router');
var routes = require('./config/routes.jsx');

Router.run(routes, Router.HistoryLocation, function(Handler) {
  React.render(<Handler/>, document.body);
});

The "run()" function will evaluate the routes in it's first argument, compare them to a location, and then call back with the appropriate view to be rendered. The second argument is just a configuration option telling React-Router to generate clean URLs with HistoryLocation instead of the default HashLocation. Now when you visit http://localhost:8080 you should see the navigation menu we created, and be able to navigate between the root path and the "about" path. If you want to test the responsive "menu-link", reduce the width of your browser window until the menu is hidden, and then toggle it with the link.


★ Here is a progress checkpoint for "Adding Routes to React".


Authentication with Twitter OAuth and JSON Web Tokens

For me, authentication with a server that has no state was always the elephant in the room when thinking about this architecture. It's the least intuitive aspect when you're accustomed to simply calling "current_user" on the server. Furthermore, the most widely used gem for authentication, Devise, and the most widely used gem for negotiating with identity providers like Twitter and Facebook, Omniauth, are both unusable because they both depend on the existence of a server session.

Let's tackle identity first. Twitter does not support OAuth directly from the browser, as they've determined it can't be done securely, so we need to negotiate from the server. To do that we need a Ruby OAuth library other than Omniauth, and there are a few, but none of them are actively maintained, which is generally a good indication that you've entered somewhat less charted territory. After a bit of digging, it seems like the best candidate for active maintenance going forward is oauth-ruby, which was recently taken over by Michal Papis, who has clearly done a great job since taking over RVM from Wayne Seguin.

So we will negotiate with Twitter once per browser session to find out who is using our app, but how do we authenticate each subsequent request between React and the API? In other words, after someone has already signed in with Twitter, when they decide to click another link in our app, how do we determine whether the server should respond with "200 OK" or "401 Unauthorized"? Twitter OAuth solves identity, but we have yet to solve authentication. As mentioned at the outset, we will solve authentication with JSON Web Tokens, an emerging standard for the URL-safe transfer of signed claims between parties. More on that later, for now lets just add these two libraries to our Gemfile as follows:

# unchanged other gems
gem 'oauth'
gem 'jwt'

and from the Terminal run:

bundle install

The process of obtaining a single access token from Twitter requires more than one request to their servers to complete, and we'll need to persist some data in between that series of requests, without a session, so we'll need a table in the database for that data. We'll also need a user model for a number of reasons, so let's go ahead and create both of those tables now. Run each of these commands in Terminal:

rails g migration create_oauths
rails g migration create_users

and then modify the file "db/migrate/{timestamp}_create_oauths" as follows:

class CreateOauths < ActiveRecord::Migration
  def change
    create_table :oauths do |t|
      t.string :token, null: false
      t.string :secret, null: false
    end
    add_index :oauths, :token
  end
end

and modify the file "db/migrate/{timestamp}_create_users" as follows:

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :uid, null: false
      t.string :handle, null: false
      t.timestamps null: false
    end
    add_index :users, :uid
  end
end

and finally, migrate the database from Terminal:

rake db:migrate

Now for the models, create the file "app/models/oauth.rb" and enter:

class Oauth < ActiveRecord::Base
  validates_presence_of :token, :secret
end

and then create the file "app/models/user.rb" and enter:

class User < ActiveRecord::Base
  validates_presence_of :uid, :handle
end

We also need to do some configuration for Twitter. Visit the admin page for your Twitter apps and click on the app you created for this project. Select the "Settings" tab and in the field called "Callback URL" enter http://127.0.0.1:3000/access_token and make sure to check the box that says "Allow this application to be used to Sign in with Twitter". Then create the file "config/initializers/twitter_oauth.rb" and enter:

TWITTER = OAuth::Consumer.new(
  Rails.application.secrets.twitter_consumer_key,
  Rails.application.secrets.twitter_consumer_secret,
  authorize_path: '/oauth/authenticate',
  site: 'https://api.twitter.com'
)

This creates an instance of the OAuth client with our Twitter credentials, and gives it the URL Twitter can be reached at for OAuth. The last piece of configuration is to put the callback URL and our application's origin into environment variables, because the values will change between development and production, and we need them during the OAuth negotiation. Open up "config/environments/development.rb" and add the following two lines:

Rails.application.configure do
  # unchanged other configs

  # Set environment secrets
  ENV['ORIGIN'] = 'http://localhost:8080'
  ENV['OAUTH_CALLBACK'] = 'http://127.0.0.1:3000/access_token'
  # unchanged environment secrets
end

We set the "origin" prop in React to http://localhost:3000, because React's requests need to go to the Rails server, but we set the "ORIGIN" environment variable in Rails to http://localhost:8080, because when it redirects after authenticating it needs to point the browser to the React client. The "OAUTH_CALLBACK" is used during negotiation and must match the "Callback URL" from the Twitter admin page.

With the configuration out of the way, we can get to the guts of the authentication code. Create the file "app/controllers/tokens_controller.rb" and enter:

class TokensController < ApplicationController

  def request_token
    request_token = TWITTER.get_request_token(oauth_callback: ENV['OAUTH_CALLBACK'])
    Oauth.create(token: request_token.token, secret: request_token.secret)
    redirect_to request_token.authorize_url(oauth_callback: ENV['OAUTH_CALLBACK'])
  end

  def access_token
    oauth = Oauth.find_by(token: params[:oauth_token])
    if oauth.present?
      request_token = OAuth::RequestToken.new(TWITTER, oauth.token, oauth.secret)
      access_token = request_token.get_access_token(oauth_verifier: params[:oauth_verifier])
      user = User.find_or_create_by(uid: access_token.params[:user_id]) { |u| u.handle = access_token.params[:screen_name] }
      jwt = JWT.encode({uid: user.uid, exp: 1.day.from_now.to_i}, Rails.application.secrets.secret_key_base)
      redirect_to ENV['ORIGIN'] + "?jwt=#{jwt}"
    else
      redirect_to ENV['ORIGIN']
    end
  end
end

Most of the authentication logic correlates directly to the steps in this sign in flow from Twitter's docs. Step 1 is obtaining a request token, so in the "request_token" action we call Twitter with our OAuth client and then store the token and the secret that Twitter responds with in the database. Step 2 in the docs is redirecting the user, so we call the authorize_url() method on the request token object, which generates the URL of a Twitter login page associated with our application to send the user to. After the user logs in, Twitter will send them to the ENV['OAUTH_CALLBACK'] URL that we gave them, which is our "access_token" action. Step 3 in the docs is upgrading the request token to an access token. To do that, we first need to recreate the original request token, so we look up the record we stored during the last action using the "oauth_token" param in Twitter's callback, and then create a new request token object with that token's secret. Now we can call get_access_token() on our new request token object using the "oauth_verifier" param in Twitter's callback to upgrade to an access token. If that succeeds, we can use the access token to get information about the user that just logged in, but the access token immediately gives us their "user_id" and "screen_name" which are all we care about for the purposes of this demo app, so we've finished dealing with Twitter.

In the three lines directly above the keyword "else" inside the "access_token" action, we will begin to handle authentication for all subsequent requests between the React client and the API (in the initial publication of this tutorial, these three lines were broken out into their own action called "client_token" to draw a clear distinction between OAuth related code and JWT related code, but that implementation introduced a security vulnerability that Jens Hausherr called out in the comments). First we find or create a user based on the UID from Twitter, and if we're creating then the Twitter handle is also stored. Next we encode the UID as the payload of a Ruby JWT that is set to expire in one day, using Rails' secret key base as a convenient JWT encoding secret. JWT payloads are signed but not encrypted, so they should never contain secret information like passwords, only non-secret information that needs to be verified via the signature. UIDs are ideal payloads because they're not sensitive and they uniquely identify users. The expiration feature of JWTs is great because the alternative would be to store a record in the database with an "expires_at" attribute, but with JWTs nothing needs to be persisted. Finally, we return the user to React with a URL parameter containing the JWT.

None of these actions have routes yet so let's set them up now, as well as a route for "current_user" that we'll use shortly. Open up "config/routes.rb" and enter:

Rails.application.routes.draw do
  match '*all', to: 'application#preflight', via: [:options]

  get 'current_user', to: 'application#current_user'
  get 'request_token', to: 'tokens#request_token'
  get 'access_token', to: 'tokens#access_token'

  resources :blabs, only: [:index]
end

Picking up where the "access_token" action left off, the user is now back in React with a JWT in the URL, so we need some way to persist the JWT in the client for use on subsequent requests to the API. This logic will have application wide repercussions so it should live in the root component. Open up "client/components/layout/App.jsx" and modify it as follows:

// unchanged other requires
var Uri = require('jsuri');

module.exports = React.createClass({
  getDefaultProps: function() {
    // unchanged
  },
  getInitialState: function() {
    return {showMenu: false, signedIn: false, currentUser: {handle: ''}};
  },
  componentWillMount: function() {
    var jwt = new Uri(location.search).getQueryParamValue('jwt');
    if (!!jwt) {sessionStorage.setItem('jwt', jwt);}
  },
  componentDidMount: function() {
    if (!!sessionStorage.getItem('jwt')) {this.currentUserFromAPI();}
  },
  currentUserFromAPI: function() {
    this.readFromAPI(this.props.origin + '/current_user', function(user) {
      this.setState({signedIn: true, currentUser: user});
    }.bind(this));
  },
  readFromAPI: function(url, successFunction) {
    Reqwest({
      // unchanged other fields
      headers: {'Authorization': sessionStorage.getItem('jwt')},
      // unchanged other fields
    });
  },
  handleMenuClick: function() {
    // unchanged
  },
  render: function () {
    var menu = // unchanged

    return (
      <div id="app" className={menu}>
        <Menu origin={this.props.origin} sendMenuClick={this.handleMenuClick} signedIn={this.state.signedIn} />
        <div id="content">
          <RouteHandler origin={this.props.origin} readFromAPI={this.readFromAPI} signedIn={this.state.signedIn} />
        </div>
      </div>
    );
  }
});

There's a lot of new code here so let's take it step by step. First we add a require for jsUri to help us pull the JWT out of the URL. In getInitialState() we add two new pieces of state called "signedIn" and "currentUser" and set their default values. Then in componentWillMount() we use jsUri to check if there's a JWT in the URL, and if there is, we store it in sessionStorage. The appropriate client storage mechanism is fair to debate, but in this case sessionStorage is preferable to application memory because it will persist through a browser refresh and application memory will not, preferable to localStorage because it will be purged when the browser window or tab is closed and localStorage will not, and preferable to cookies because cookies are intended for data that will read by the server not the client. Furthermore, no code from any other browser, window, tab, domain, or process will be able to access our user's sessionStorage, and React does not generate HTML strings so XSS protection is the default. Even if all of that is somehow magically circumvented, we have not encoded sensitive data in the JWT so the fallout would be minimal.

Proceeding to componentDidMount(), we now check sessionStorage for a JWT, and then call currentUserFromAPI() if we find one. The reason this code is in componentDidMount() rather than componentWillMount() is that we need to ensure it executes after we set sessionStorage, and also because componentWillMount() fires before the initial render so we don't want it to include a round trip to the API. In currentUserFromAPI(), we use the readFromAPI() function with an endpoint of "current_user" that we haven't built yet, but you can see that a successful call will respond with a user object, and we assign that object to the "currentUser" state and set "signedIn" to true. We have also added a field to readFromAPI() called "headers" in order to to set a request header called "Authorization" with whatever is returned from sessionStorage.getItem('jwt'), which will return either null, an empty string, an expired JWT, or a valid JWT. For anything other than a valid JWT, the API should respond with "401 Unauthorized". Finally in render(), we add a "signedIn" prop to the RouteHandler and Menu component.

Moving back to the API, we need to check the authorization header whenever "current_user" is requested. In "app/controllers/application_controller.rb" enter:

class ApplicationController < ActionController::API
  # unchanged before_action
  before_action :authenticate_request, only: [:current_user]

  def preflight
    # unchanged
  end

  def current_user
    render json: @current_user, only: [:handle]
  end

private
  def allow_cross_origin_requests
    # unchanged
  end

  def authenticate_request
    begin
      uid = JWT.decode(request.headers['Authorization'], Rails.application.secrets.secret_key_base)[0]['uid']
      @current_user = User.find_by(uid: uid)
    rescue JWT::DecodeError
      render json: 'authentication failed', status: 401
    end
  end
end

We put the authentication logic in a private method in ApplicationController so that it can be reused in any child controller that requires an authenticated user. In this controller, it should run before "current_user" but not before "preflight", so we restrict the "before_action" accordingly. In the "authenticate_request" method, we read the JWT from of the headers and decode it with the application secret. If decoding succeeds, we can access the payload, which we use to lookup a user that we assign to an instance variable called @current_user, and then the "current_user" action will simply respond with @current_user. If Ruby-JWT throws any decoding errors, "authenticate_request" will immediately render a 401, which halts the request cycle so the "current_user" action never runs.

The final step in this process is to create "Sign In" and "Sign Out" links for the user to click. Open up "client/components/layout/Menu.jsx" and enter:

// unchanged requires

module.exports = React.createClass({
  handleSignOutLink: function() {
    sessionStorage.setItem('jwt','');
    location = '/';
  },
  render: function() {
    if (this.props.signedIn) {
      var signingLink = <li><span onClick={this.handleSignOutLink}>Sign Out</span></li>;
    } else {
      var signingLink = <li><a href={this.props.origin + '/request_token'}>Sign In</a></li>;
    }

    return (
      <div id="menu">
        <span id="menu-link" onClick={this.props.sendMenuClick}><span></span></span>
        <div id="menu-list">
          <div className="pure-menu pure-menu-open">
            <span className="pure-menu-heading">Blabber</span>
            <ul>
              <li><Link to="blabs">Blabs</Link></li>
              <li><Link to="about">About</Link></li>
              {signingLink}
            </ul>
          </div>
        </div>
      </div>
    );
  }
});

Just inside the render() function, we create some markup for authentication related links that will change based on the "signedIn" state, and then render that markup below the other navigation links. If a user is signed out, they will have a "Sign In" link that points to the "request_token" endpoint, which will begin the OAuth negotiation with Twitter that we walked through earlier. If a user is signed in, they will have a "Sign Out" link that simply clears out the JWT in sessionStorage, and then visits the root path to reset all of the state contained in the root component, which will propagate throughout the application. This state resetting also occurs after a user returns to the root path following a successful Twitter login. This should be everything we need to handle authentication, so start up both of the development servers and get to clicking.

From now on, you will need start the Rails server with "rails s -b 127.0.0.1" (make a bash alias) instead of just "rails server". In previous versions of Rails the server would bind to the IP "0.0.0.0" by default and therefore be reachable at any IP addresses on the local machine, but as of Rails 4.2.0 the server binds to "localhost" by default, which will be unreachable when Twitter calls back to http://127.0.0.1:3000/access_token, and Twitter will not accept "localhost" in the "Callback URL" setting on the admin page, so we have to explicitly bind the Rails server to either "0.0.0.0" or "127.0.0.1" with the "-b" flag.

After running "rails s -b 127.0.0.1" in one Terminal tab and "npm run devserve" in another, you should be able to log in and out of Twitter through your React app. You have now slain the dragon of stateless API authentication with a third party identity provider. Go have a beer, or some ice cream or whatever.


★ Here is a progress checkpoint for "Authentication with Twitter OAuth and JWT".


Writing to the API

Now that our React app has a "currentUser" in state, we can create new blabs associated with an author. As with reading from the API, writing to the API will likely be application wide functionality so we'll add that logic to the root component. Open up "client/components/layout/App.jsx" and enter:

// unchanged requires

module.exports = React.createClass({
  getDefaultProps: function() {
    // unchanged
  },
  getInitialState: function() {
    // unchanged
  },
  componentWillMount: function() {
    // unchanged
  },
  componentDidMount: function() {
    // unchanged
  },
  currentUserFromAPI: function() {
    // unchanged
  },
  readFromAPI: function(url, successFunction) {
    // unchanged
  },
  writeToAPI: function(method, url, data, successFunction) {
    Reqwest({
      url: url,
      data: data,
      type: 'json',
      method: method,
      contentType: 'application/json',
      headers: {'Authorization': sessionStorage.getItem('jwt')},
      success: successFunction,
      error: function(error) {
        console.error(url, error['response']);
        location = '/';
      }
    });
  },
  handleMenuClick: function() {
    // unchanged
  },
  render: function () {
    var menu = // unchanged

    return (
      <div id="app" className={menu}>
        <Menu origin={this.props.origin} sendMenuClick={this.handleMenuClick} signedIn={this.state.signedIn} />
        <div id="content">
          <RouteHandler origin={this.props.origin} readFromAPI={this.readFromAPI} writeToAPI={this.writeToAPI} currentUser={this.state.currentUser} signedIn={this.state.signedIn} />
        </div>
      </div>
    );
  }
});

The writeToAPI() function is almost identical to readFromAPI(), except that it adds a "method" argument so it can be used with PUT or POST requests, and a "data" argument for whatever is being written. We also need to add "writeToAPI" and "currentUser" to the props that we pass the RouteHandler. Moving downstream to the BlabsView component, open up "client/components/blabs/View.jsx" and enter:

// unchanged other requires
var BlabsForm = require('./Form.jsx');

module.exports = React.createClass({
  getInitialState: function() {
    // unchanged
  },
  componentDidMount: function() {
    // unchanged
  },
  readBlabsFromAPI: function() {
    // unchanged
  },
  writeBlabToAPI: function(data) {
    this.props.writeToAPI('post', this.props.origin + '/blabs', data, function(blab) {
      var blabs = this.state.data;
      blabs.unshift(blab);
      this.setState({data: blabs});
    }.bind(this));
  },
  render: function() {
    return (
      <div className="blabs-view">
        <BlabsForm writeBlabToAPI={this.writeBlabToAPI} signedIn={this.props.signedIn} />
        <BlabsList data={this.state.data} />
      </div>
    );
  }
});

The first change to BlabsView is a new require statement for a BlabsForm component that we will build shortly. The BlabsView component is where writeToAPI() will actually be called from, because it's solely responsible for all blab related state, but writeToAPI() is called inside of a more specific function named writeBlabToAPI() that we pass down to BlabsForm where the data will actually be submitted from by the user. We also need to pass the "signedIn" state to the form because it's behavior will depend on that state. The "successFunction" we pass to writeToAPI() simply puts the current state of "data" in a variable, adds the newly created blab that is returned from the API to the beginning of the array, and sets the new state of "data", which will trigger an automagic React render.

To make BlabsForm, create the file "client/components/blabs/Form.jsx" and enter:

var React = require('react');

module.exports = React.createClass({
  handleSubmit: function(e) {
    e.preventDefault();
    var content = this.refs.content.getDOMNode().value.trim();
    if (!content) {return;}
    if (this.props.signedIn) {
      this.props.writeBlabToAPI(JSON.stringify({blab: {content: content}}));
      this.refs.content.getDOMNode().value = '';
      this.refs.content.getDOMNode().blur();
    } else {
      alert('Please sign in to blab!');
    }
  },
  render: function() {
    return (
      <form className="blabs-form pure-form" onSubmit={this.handleSubmit}>
        <input type="text" placeholder="start blabbing..." ref="content" />
        <button type="submit" className="pure-button pure-button-primary">Blab</button>
      </form>
    );
  }
});

This component introduces a new React concept called refs, which are simply references to DOM nodes, allowing you to interact with them after they have been rendered. In this case we're referencing a text input node with a ref we've named "content", so that we can pull user input data out of it. When a user submits the form, we put their input in a variable using the "content" ref, and if it's empty we break out of execution immediately. If it's not empty, we check that they are signed in because we need an author to associate the blab with, and if they're not we alert them to sign in. If they are signed in, we call the writeBlabToAPI() function passed down by BlabsView with their input as an argument, clear the input field, and remove focus.

We just need to make a few quick changes to the way we are rendering blabs now that they'll have authors. Open "client/components/blabs/List.jsx" and add an "author" prop to the return value of the blabs variable:

var blabs = this.props.data.map(function(blab) {
  return (
    <Blab key={blab.id} content={blab.content} author={blab.user.handle} />
  );
});

Notice that we are accessing the author's handle by calling ".user" on the blab, so we are now expecting the API to include a blab's associated user in the JSON, which we will take care of shortly. To render the author in each individual blab, open up "client/components/blabs/Blab.jsx" and add it inside of the li tag:

<li className="blab">
  <span className="blab-author">{this.props.author}: </span>
  <span className="blab-text">{this.props.content}</span>
</li>

At this point we've finished all of the client side code for this feature, so let's move back to the API. The first thing we should do is add a foreign key to the blabs table so open up "db/migrate/{timestamp}_create_blabs" and modify it as follows:

create_table :blabs do |t|
  t.belongs_to :user, index: true
  # unchanged other attributes
end

Run this entire line in Terminal to dump the old data and update the schema:

rake db:drop && rake db:create && rake db:migrate

Now declare the associations in "app/models/blab.rb" and "app/models/user.rb":

class Blab < ActiveRecord::Base
  belongs_to :user
  # unchanged validations
end
class User < ActiveRecord::Base
  has_many :blabs
  # unchanged validations
end

Pretty much the entire blabs controller needs to change so we can render authors from the index action and handle POST requests from React, so open up "app/controllers/blabs_controller.rb" and enter:

class BlabsController < ApplicationController
  before_action :authenticate_request, only: [:create]

  def index
    @blabs = Blab.all.order(created_at: :desc)
    render json: @blabs, include: { user: { only: [:handle] } }, only: [:id, :content]
  end

  def create
    @blab = @current_user.blabs.build(blab_params)
    if @blab.save
      render json: @blab, include: { user: { only: [:handle] } }, only: [:id, :content], status: :created, location: blab_url(@blab, format: :json)
    else
      render json: @blab.errors, status: :unprocessable_entity
    end
  end

private
  def blab_params
    params.require(:blab).permit(:content)
  end
end

You can see in the index action we're now including the associated user with each blab, and restricting the payload to the minimum amount of data we can send over the wire for React to render what it needs to render. Before the create action, we're running the "authenticate_request" method from ApplicationController to make sure there's an authenticated user to build the blab off of, and if the JWT decoding succeeds, we know we will have access to the @current_user instance variable. If blab creation succeeds, we return the newly created blab and it's associated user to React, along with the appropriate response code and location of the record. The private method "blab_params" is standard stuff that should look familiar from Rails. We're almost home, we just need to add a route for the create action for obvious reasons, and a route for the show action so we can use the URL helper in the response location of the create action, so modify "config/routes.rb" as follows:

Rails.application.routes.draw do
  # unchanged other routes
  resources :blabs, only: [:index, :create, :show]
end

You should now be able to run "rails s -b 127.0.0.1" in one Terminal tab and "npm run devserve" in another, and after logging in with Twitter, write some blabs on the wall. Woot! Just two quick tricks left before we can deploy to Heroku.


★ Here is a progress checkpoint for "Writing to the API".


Optimistic Updates

React makes it so trivial to update your application's state, that we can easily update the state of our blab data before we even submit a POST request with a new blab to the API. This is called an optimistic update, because it renders new data before it has cleared server side validations on the assumption that it will be valid. Then as soon as the validated data comes back from the server, we'll update state again to replace the optimistic update. To implement this, we only need to touch two React files. First modify "client/components/blabs/View.jsx" as follows:

writeBlabToAPI: function(data) {
  this.props.writeToAPI('post', this.props.origin + '/blabs', data, function(blab) {
    var blabs = this.state.data;
    blabs.shift();
    blabs.unshift(blab);
    this.setState({data: blabs});
  }.bind(this));
},
optimisticUpdate: function(blab) {
  var blabs = this.state.data;
  blabs.unshift(blab);
  this.setState({data: blabs});
},
render: function() {
  return (
    <div className="blabs-view">
      <BlabsForm writeBlabToAPI={this.writeBlabToAPI} optimisticUpdate={this.optimisticUpdate} currentUser={this.props.currentUser} signedIn={this.props.signedIn} />
      <BlabsList data={this.state.data} />
    </div>
  );
}

Starting from the top, in writeBlabToAPI(), all we've done is add a single line of code, the line that says "blabs.shift();". This line will remove the first element from the array of blab data, which we know with certainty will be the optimistic update because it would be impossible for a round trip to the server to complete before JavaScript can execute the optimisticUpdate() function. All the optimisticUpdate() function does is take a blab as an argument, prepend it to the existing array of blab data, and reset the state of "data". As with the writeBlabToAPI() function, optimisticUpdate() will be executed from BlabsView which owns all blab related state, but it will be passed down to BlabsForm as prop, along with "currentUser", which will be needed to construct the optimistic update. Now we just add one line of code to "client/components/blabs/Form.jsx" as follows:

if (this.props.signedIn) {
  this.props.optimisticUpdate({id: 'fake-id', content: content, user: this.props.currentUser});
  this.props.writeBlabToAPI(JSON.stringify({blab: {content: content}}));
  this.refs.content.getDOMNode().value = '';
  this.refs.content.getDOMNode().blur();
} else {
  alert('Please sign in to blab!');
}

We added the first line inside the "if" statement that checks if the user is signed in. As you can see, the line we added calls the optimisticUpdate() function with a JSON object containing an arbitrary string as a fake id, the content from the input node, and the currently signed in user to pull the handle from as the author. The fake id satisfies React's previously discussed requirement for a key attribute on items in a collection, and we know it will be unique because the other keys will all be ActiveRecord primary keys, which are integers obviously.

That's it! Now the list of blabs will update instantaneously with a newly submitted blab, even in the context of a slow network.


★ Here is a progress checkpoint for "Optimistic Updates".


Loading React Outside of the Root Path

There is a major flaw in our architecture right now. To demonstrate the flaw, we'll need to simulate the production environment, so instead of using two tabs in Terminal with the Webpack dev-server, we're going to go back to just using the Rails server, but first run the build script as follows:

npm run devbuild

You will now have two new files in the public directory, "bundle.js" and "bundle.js.map". Start only the Rails server and visit http://localhost:3000 like we did at the very beginning. If you refresh the browser, React will reload no problem, but if you visit the "/about" view and then refresh the browser, you will get an ActionController error saying 'No route matches [GET] "/about"'. When you click the "About" link in the navigation, React is already loaded and React-Router handles the request to "/about", but when you refresh the browser from that page, the assets need to be re-delivered, and they cannot be because when the browser requests "/about" the server pukes, because we never gave it a route to match "/about".

This seems like a huge mess but it can actually be solved quite efficiently. First we need another catch-all route so modify "config/routes.rb" as follows:

Rails.application.routes.draw do
  # unchanged other routes
  match '*all', to: 'application#index', via: [:get]
end

Remember order matters in the routes file, so this catch-all route must be the last line of the routes file inside the closing "end". That way, it will only match routes after every other route in the file has been tried already, and we are also restricting it to GET requests only. We send any of these requests to an action called "index" that we'll make now. Inside "app/controllers/application_controller.rb" add:

def index
  render file: 'public/index.html'
end

This action can go anywhere inside ApplicationController above the "private" keyword, like after the "preflight" action for example, and will render the entire index.html DOM that would have rendered at the root path, so "bundle.js" will be requested, React will load, and React-Router will handle the request to "/about". Sorted. Thanks Rails! We've finished this fix so you can now delete "bundle.js" and "bundle.js.map" and return to using the normal development servers.

There's a ton we could still do to improve the UX and error handling of this demo app, but this loooooooooooong ass post needs to end, so let's wrap it up. If you're not interested in deploying this project, you can stop now. You won programming! If you are interested in deploying this project, once more unto the breach and let's ship some code.


★ Here is a progress checkpoint for "Loading React Outside of the Root Path".


Deploying to Heroku

The first step is to create a free Heroku account if you don't already have one, and then install the Heroku toolbelt. After that's done, create a Heroku app and name it whatever you like. Now open Terminal and run:

heroku git:remote -a your_app_name

Now you have a Heroku remote that you can see by running "git remote -v" in Terminal, but we have some more work to do before you can push to it. The next step is to modify the Gemfile as follows:

# unchanged other gems
group :production do
  gem 'rails_12factor'
end

Then install these gems that Heroku requires from Terminal by running:

bundle install

Now create a file at the root called "Procfile" and enter:

web: bundle exec puma -C config/puma.rb

The Procfile just lets you declare a process type, in this case we're referencing our Puma server config file. Next up is to modify "package.json" as follows:

"scripts": {
  "devserve": "webpack-dev-server -d --config webpack.dev.config.js --content-base public/ --progress --colors",
  "devbuild": "webpack -d --config webpack.dev.config.js --profile --progress --colors",
  "probuild": "webpack -p --config webpack.pro.config.js --profile --progress --colors",
  "postinstall": "npm run probuild && npm run rejoice",
  "rejoice": "echo yay!"
},

We have added three lines of code here. One is the "probuild" field in which we run our production Webpack configuration. Heroku will automatically look for a "postinstall" field in "package.json" and execute the commands therein, so we use that field to tell Heroku to run our Webpack production script, and then print "yay!" to the logs. Then we need to specify a custom buildpack because Heroku cannot automatically detect that we want to run a Node and then Ruby buildpack in sequence. The buildpack that will allow us to do that is the heroku-buildpack-multi, which we can tell Heroku to use by running the following in Terminal:

heroku config:add BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git

The heroku-buildpack-multi will expect a file at the root called ".buildpacks" that points to multiple buildpacks, so create that file now and enter:

https://github.com/heroku/heroku-buildpack-nodejs.git https://github.com/heroku/heroku-buildpack-ruby.git

Our ".buildpacks" file contains the URLs of two buildpacks, which Heroku will use in sequence from left to right. So it will build our React app with the Node buildpack, and then our Rails app with the Ruby buildpack.

The last step is to create another Twitter app to get a new "Callback URL" for OAuth in production, and get a new set of keys. If your Heroku app name were "blabber" for example, the "Callback URL" you would set on the Twitter admin page would be https://blabber.herokuapp.com/access_token and don't forget to check the box that says "Allow this application to be used to Sign in with Twitter". Then we need to add the OAuth keys and a few other environment variables to Heroku by running the following commands in Terminal one at a time:

heroku config:add ORIGIN=""
heroku config:add OAUTH_CALLBACK=https://your_app_name.herokuapp.com/access_token
heroku config:add TWITTER_CONSUMER_KEY=your_consumer_key
heroku config:add TWITTER_CONSUMER_SECRET=your_consumer_secret

The ORIGIN environment variable is being set to an empty string so relative paths will be used in production. You can check that all of your environment variables are set correctly by running "heroku config" in Terminal. That takes care of all the configuration, just two commands left. First make sure to commit all changes to git, and then run:

git push heroku master

That will take a minute to complete. You will see the Node buildpack run and then Ruby buildpack run in the logs. When it's finished run:

heroku run rake db:migrate

Your app is now ready to party. That was more work than the average Heroku deploy, but still amazingly easy when you consider the complexity of what we just accomplished with a few lines of configuration.


★ Here is a progress checkpoint for "Deploying to Heroku".


Wrapping Up

If you're familiar with React, you may have noticed that we haven't discussed Flux at all. Hopefully that demonstrated that you don't actually need anything other than React to build things with React. That said, if you're building anything moderately complex, the approach we took for this simple demo would probably be too naive, so Flux is worth exploring. The React ecosystem is evolving rapidly and it's worth keeping an eye on a few projects, most notably, the first offical implementation of Flux called Relay, which is due in the near term, and of course React Native, which should also be available soon. Another project worth checking out is a hybrid solution for sharing the same code base between web and native called Reapp that recently gained over 1,000 stars on Github in around a week.

That's it for this post. If you got this far I hope it was helpful!

Never miss a post!