React + Flux Backed by Rails API - Part 1
I’ve been working on a frontend for a project we are developing here at Fancy Pixel. We are embracing what looks like a good habit: slicing what would be a monolithic Rails app in a lightweight backend serving APIs and a frontend consuming them. We did this in the not so distant past using Angular.js. It was all fine and dandy, until it wasn’t. There’s something about it that doesn’t sit right with me, I wouldn’t go in detail, since many others already did, but let’s just say that there’s too much magic involved for my tastes (says the guy using Rails). Magic is fine as long as I can figure out how to tinker with the internals when things go down south. With Angular the effort seems too much, but that’s just personal taste really. Also I can’t deny that the major structural changes introduced in 2.0 were the last nail in the coffin.
I wanted to try something new, something that would enforce a solid architecture of our apps, letting me control the single cogs in the engine. React got a lot of good press in the past months, so I took the chance to dive in. In this three-part post you’ll find pretty much everything I learned by writing a frontend using React, with a vanilla Flux architecture, consuming an API written in Rails.
Choosing the backend
Given our experience, the obvious choice for us was Rails, but with a twist: rails-api. Rails-api is a stripped down version of Rails, where most of the ‘useless’ middleware is not included (but you can include it might you need it). Using Rails to serve JSON might seem overkill to most, but the Github page of rails-api has some really good points to counter this argument, and I think they are spot on. TL;DR version: Rails is awesome, let’s take advantage of that.
The frontend technology
React is javascript library for building user interfaces built and open sourced by the Facebook’s engineers. Its major selling point is the ability to provide a dynamic and fast way to build isomorphic apps.
Isomorphic means that the app can be rendered with ease on both the server and the client, which helps with SEO.
Personally, I couldn’t care less about SEO, even if I understand how important it is… I was really sold on React by the Virtual DOM and how the data is organized and handled in React views.
The Virtual DOM is something that we’re going to see implemented in other JS frameworks (Ember does that already if I’m not mistaken). The views can be rendered on the server for the initial request, than the underlying tech is going to render subsequent pages in a Virtual DOM, that is then diffed with the actual DOM, and then only the differences are changes in the visible page. And it’s fast. Brilliant. This enables us to start writing frontend like we used way back: in a declarative way… we just specify how the UI should look, when data changes React takes care of the page refresh, changing only the parts that need to be changed.
Flux
This covers the backend and the views, we’re missing something in between, say, an architecture to follow. Flux is an architecture for building web UIs, and works really well in combination with React (but it can really be applied anywhere).
Here comes trouble
I never was a big fan of implementing web UI, CSS always gets messy, Javascript files become scary monoliths where crappy code goes to die, while developers test their spelunker skills and loose their sanity. Maybe I’m just crap, but even using Sass and Coffeescript never really solved my issues.
I was excited to try something really new, but I knew that getting started with such a young technology would end up being a major pain in the ass.
Case and point, learning and starting being productive (i.e.: writing usable code) took a fair bit of head scratching. There’s still no clear “best practice” to perform common tasks, nor a clear starting configuration. Let’s put it that way, if you come from the RoR world, where convention over configuration greatly reduces boilerplating and “forces” you to follow commonly established best practice, you’re going to struggle with Flux.
This post will cover the solutions we came up with, they may not be perfect, but I’m fairly sure there are no anti-patterns in there, and they are a solid starting point.
Getting it all toghether
Let’s start writing some code. We’ll go through a simple Rails app that will feature user signup and login, and the ability to post a story. Just like a tiny Medium. Let’s appropriately call it Small.
Feel free to skip the Railsy part if you’re only interested in Flux and React and jump to Part 2.
Rails API
A while ago I stumbled upon this article by Alan Peabody. I had a similar experience as him, you start working ona project, you use the right tools, you do your best to enforce good patterns, but in the end the frontend code just becomes scary, something no one wants to maintain. Let’s break up with the asset pipeline, as the title says, and work towards making Rails beautiful again. We’ll be using the rails-api gem for this task. You can generate a new app with its CLI command, or you can integrate it later. I’ll do the later option, no reason really, just a habit.
rails new small
Next we’ll add rails-api
, devise
, active_model_serializers
gems to our Gemfile, and while we are at it we can remove all the gems that generate assets or view content, jbuilder
included. Our Gemfile should look like this (test section omitted):
source 'https://rubygems.org'
gem 'rails', '4.2.0'
gem 'rails-api', '~> 0.4.0'
gem 'active_model_serializers', '~> 0.8.3' # NOTE: not the 0.9
gem 'devise', '~> 3.4.1'
gem 'sqlite3'
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'thin'
group :development, :test do
gem 'faker'
gem 'byebug'
gem 'web-console', '~> 2.0'
gem 'spring'
end
Now we need to change the application controller so that it inherits from ActionController::API
, and kiss the protect_from_forgery
goodbye. Since we are serving only JSON, it makes sense to add
respond_to :json
to the applciation controller, helps DRYing all out. While we are at it, we might as well delete the assets and views folders, we won’t need them.
Authentication
Should I first define a resource? Maybe, but that’s trivial, let’s get the authentication out of the way. We are building an API, so no session will be involved, we have to authenticate the user in each request. I’ll be using Oauth2 Resource Owner Password Credentials Grant which sounds fancy, but it’s really just a token in the request header that authenticates the caller.
The gem Devise used to implement a token_authenticatable
strategy, but it was pulled for security reason. There are gems that implement the strategy (like Doorkeeper), but since it’s fairly easy to implement I’ll do it for myself. Let’s install Devise first by adding it in the Gemfile and launching rails generate devise:install
after a bundle install
, then we create the user model:
rails generate devise User
Token authentication
Token authentication was removed from Devise a couple of years ago, this link explains why. We have to implement it for ourselves, but it’s quite easy. The token will be composed of two informations: the user’s id followed by the token itself, separated by a :
. We’ll be using the user’s database id for this sample, for simplicity’s sake, but it’s obviously not a smart thing to do.
First things first, we’ll add an access_token
to the user (and a username too):
class AddAccessTokenToUser < ActiveRecord::Migration
def change
add_column :users, :access_token, :string
add_column :users, :username, :string
end
end
and here’s the User model:
# app/models/user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :recoverable, :validatable
after_create :update_access_token!
validates :username, presence: true
validates :email, presence: true
private
def update_access_token!
self.access_token = "#{self.id}:#{Devise.friendly_token}"
save
end
end
The user authentication will sit in the application controller:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include AbstractController::Translation
before_action :authenticate_user_from_token!
respond_to :json
##
# User Authentication
# Authenticates the user with OAuth2 Resource Owner Password Credentials Grant
def authenticate_user_from_token!
auth_token = request.headers['Authorization']
if auth_token
authenticate_with_auth_token auth_token
else
authentication_error
end
end
private
def authenticate_with_auth_token auth_token
unless auth_token.include?(':')
authentication_error
return
end
user_id = auth_token.split(':').first
user = User.where(id: user_id).first
if user && Devise.secure_compare(user.access_token, auth_token)
# User can access
sign_in user, store: false
else
authentication_error
end
end
##
# Authentication Failure
# Renders a 401 error
def authentication_error
# User's token is either invalid or not in the right format
render json: {error: t('unauthorized')}, status: 401 # Authentication timeout
end
end
We conclude the auth process by providing the routes and the session controller:
# config/routes.rb
Rails.application.routes.draw do
devise_for :user, only: []
namespace :v1, defaults: { format: :json } do
resource :login, only: [:create], controller: :sessions
end
end
and the session controller:
# app/controllers/v1/sessions_controller.rb
module V1
class SessionsController < ApplicationController
skip_before_action :authenticate_user_from_token!
# POST /v1/login
def create
@user = User.find_for_database_authentication(email: params[:username])
return invalid_login_attempt unless @user
if @user.valid_password?(params[:password])
sign_in :user, @user
render json: @user, serializer: SessionSerializer, root: nil
else
invalid_login_attempt
end
end
private
def invalid_login_attempt
warden.custom_failure!
render json: {error: t('sessions_controller.invalid_login_attempt')}, status: :unprocessable_entity
end
end
end
The SessionSerializer is an Active Model Serializer object, something like this:
# app/serializers/v1/session_serializer.rb
module V1
class SessionSerializer < ActiveModel::Serializer
attributes :email, :token_type, :user_id, :access_token
def user_id
object.id
end
def token_type
'Bearer'
end
end
end
That’s it. Migrate, run the server, and create a user via the console. You should get something like this:
$ curl localhost:3000/v1/login --ipv4 --data "username=user@example.com&password=password"
{
"token_type": "Bearer",
"user_id": 1,
"access_token": "1:MPSMSopcQQWr-LnVUySs"
}
Creating a resource
I won’t go in detail here, the task is just plain RoR. We’ll create a Story resource and a controller that will handle the user creation. You’ll find the complete rails app in this repo. Moving on.
CORS
It’s worth noting that the UI now will not be served by rails, it might even sit in a different server. There are solution that let us keep both UI and API on the same domain, but for now we will need to enable Cross-origin resource sharing (CORS). We can do this by adding the rack-cors gem to our Gemfile and then add this in our config/application.rb
:
config.middleware.insert_before 'Rack::Runtime', 'Rack::Cors' do
allow do
origins '*'
resource '*',
headers: :any,
methods: [:get, :put, :post, :patch, :delete, :options]
end
end
This opens up to every domain, so thread lightly.
Next up
That’s it for the Rails part. It’s such a joy writing just the API in Rails. It’s easier to test, easier to maintain and it’s blazing fast. Now we’ll proceed with the frontend. For readability’s sake I’ll split the article here, jump to Part 2 to start building the frontend.