Hi, recently I've shifted my Rails web app to React front-end and Rails API for the back-end of which the source code is now open source. So I wanted to make a tutorial for new developers to get started with this easily.

Introduction

Ruby on Rails(RoR) is losing popularity because when in massive traffic it can become slow and does not scale well. The RoR ecosystem is now improved compared to old times, but still, things are in early stages, and developers don't want to take any risks. So, the great solution developers have - use React as it's front-end and Rails API as it's back-end.

RoR web app acts as MVC Architecture and always has to generate a dynamic web page and send it to the client; this makes web app a little slower.
React acts as a View layer and Rails API will act as Model/Controller in MVC Architecture. Using this combination will make your web apps faster.

Getting Started

We'll use Ruby 2.6.1, Rails 5.2.3, React 16. If you don't have these installed, head up to GoRails Installation Guide.

Create a new rails app with webpack onfigured for react. In your terminal(don't include the $ sign while pasting the code in terminal):

$ rails new myapp -T -d mysql --skip-sprockets --webpack=react

If you see error like You must use Bundler 2 or greater with this lockfile., then additionally you need to run:

$ cd myapp
$ rails webpacker:install
$ rails webpacker:install:react

In the above command:

  • -T = Ignore the test framework which comes default with rails
  • -d mysql = Use mysql as database. You can use -d postgres for postgres DB
  • --skip-sprockets = Skips sprockets asset pipeline
  • --webpack=react = Use webpack configured for React framework

You'll see new app/javascript folder. This is where all your react code will go.

Setting up react

  1. First, we'll add react-router-dom package, which will make navigation in our React app simpler:

    $ yarn add react-router-dom
    
  2. We'll create a components folder to make our React files in a structured manner. Create components folder and create an App.js file in it. This file will act as a homepage:

    $ mkdir app/javascript/components
    $ touch app/javascript/components/App.js
    
    • Add below content in our App.js file. If you know react, it's easy to understand below code:
    // App.js
    
    import React, { Component } from 'react';
    
    class App extends Component {
      render() {
        return (
          <div>
            <h1>Hello world from App.js</h1>
          </div>
        );
      }
    }
    
    export default App;
    
  3. We'll delete the file app/javascript/packs/hello_react.jsx which comes default when we install react and create new file app/javascript/packs/index.js which will serve as Top level component in our app:

    $ rm app/javascript/packs/hello_react.jsx
    $ touch app/javascript/packs/index.js
    
    • Add below code to the file:
    // app/javascript/packs/index.js
    
    import React from 'react';
    import ReactDOM from 'react-dom';
    import { BrowserRouter as Router, Route } from 'react-router-dom';
    
    import App from '../components/App';
    
    document.addEventListener('DOMContentLoaded', () => {
      ReactDOM.render(
        <Router>
          <Route path="/" component={App} />
        </Router>,
        document.body.appendChild(document.createElement('div')),
      );
    });
    
  4. We'll now create a new rails controller which will act as Single page application for our front-end react:

    $ rails g controller pages index --no-assets --no-helper
    
    • --no-assets --no-helper line says not to include any assets in the generator(We'll use react for this) and also not to include helper methods as we're using this controller only for react front-end.
  5. We'll now configure the config/routes.rb to make pages#index as root and also allow react to handle all requests which our rails app doesn't understand.

    # config/routes.rb
    
    root 'pages#index'
    
    
    # React, this should be at last
    match '*path', to: 'pages#index', via: :all
    
  6. Now, include the JS and CSS content of react in app/views/pages/index.html.erb File. Remove all the content and add these two lines:

    <%= stylesheet_pack_tag 'index' %>
    <%= javascript_pack_tag 'index' %>
    
    • In app/views/layouts/application.html.erb file, remove below lines as we're using react and not asset pipeline for CSS and JS.
    <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
    
  7. Now it's time to set up the database. Add whatever your credentials are to config/database.yml file and then run these commands:

    $ bundle exec rake db:create
    $ bundle exec rake db:migrate
    
  8. Install foreman gem which can run multiple processors in one terminal, so we don't need separate terminals to run Rails server and Webpack dev server.

    $ gem install foreman
    
    $ touch Procfile.dev
    
    • Add these lines in Procfile.dev file. Change your port as required:
    rails: rails s -b 0.0.0.0 -p 3002
    react: bundle exec ./bin/webpack-dev-server
    
  9. Now just run below command and then you should see "Hello world from App.js" on your browser screen:

    $ foreman start -f Procfile.dev
    

Rails API Backend

We'll now set up Rails API which will serve as the backend for our app. Let's get our hands dirty:

  1. We'll start by creating Article scaffold. Scaffolding makes creating CRUD apps much easier without writing any code.

    A scaffold in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above.

    $ rails g scaffold Article title:string body:text --skip-template-engine --skip-assets --skip-helper
    $ bundle exec rake db:migrate
    
  2. Let's create some fake data to work with; we can do it with rails feature called Seeds. Add below code in db/seeds.rb file:

    Article.create(title: "Hello world 1", body: "Hello world 1 body is this content")
    
    Article.create(title: "Hello world 2", body: "Hello world 2 body is this content")
    
    Article.create(title: "Hello world 3", body: "Hello world 3 body is this content")
    
    Article.create(title: "Hello world 4", body: "Hello world 4 body is this content")
    
    Article.create(title: "Hello world 5", body: "Hello world 5 body is this content")
    
    • Now, run the command to push the seeds to the database:
    $ bundle exec rake db:seed
    
  3. The above rails scaffold command create unnecessary files in app/views/articles folder, so let's delete them all and start fresh:

    $ rm app/views/articles/*
    
  4. Let's create basic CRUD operations for our Articles. Since this is an API app, we'll also use Articles controller to respond with JSON. Open up the app/controllers/articles_controller.rb file and replace it with:

    class ArticlesController < ApplicationController
      before_action :set_article, only: [:show, :edit, :update, :destroy]
    
      skip_before_action :verify_authenticity_token, only: [:update, :create, :destroy]
    
      def index
        @articles = Article.all
        render json: {status: 'SUCCESS', message: 'All Articles Loaded', data: @articles}, status: :ok
      end
    
      def show
        render json: { status: 'SUCCESS', message: "Article #{@article.id} Loaded", data: @article }, status: :ok
      end
    
      def new
        @article = Article.new
      end
    
      def edit
      end
    
      def create
        @article = Article.new(article_params)
        if @article.save
          render json: { status: 'SUCCESS', message: "Article #{@article.id} Created", data: @article, location: @article }, status: :created
        else
          render json: { status: 'ERROR', data: @article.errors }, status: :unprocessable_entity
        end
      end
    
      def update
        if @article.update(article_params)
          render json: { status: 'SUCCESS', message: "Article #{@article.id} Updated", data: @article, location: @article }, status: :ok
        else
          render json: { status: 'ERROR', data: @article.errors }, status: :unprocessable_entity
        end
      end
    
      def destroy
        @article.destroy
        render json: { status: 'DELETED' }, status: :ok
      end
    
      private
        def set_article
          @article = Article.find(params[:id])
        end
    
        # Never trust parameters from the scary internet, only allow the white list through.
        def article_params
          params.require(:article).permit(:title, :body)
        end
    end
    
  5. That's it; Your API is almost ready. Point your browser to "http://localhost:3002/articles"(or whatever your endpoint is), and you should see JSON output of all the Articles.

    • SHOW Action: To see the Article of ID 1, point your browser to "http://localhost:3002/articles/1" - You'll see Article ID 1 in JSON.
    • CREATE Action: You can use curl command to create it:
      curl -H "Content-Type: application/json" -X POST -d '{"article":{"title":"My Title 1", "body":"My body 1"}}' http://localhost:3002/articles
      
    • DELETE Action: Delete Article with ID 3
      curl -X DELETE http://localhost:3002/articles/3
      

Using React to make API requests

Always using CURL commands to perform actions can be hectic, so we'll use react that will fetch the articles from our API and then render them on Homepage(App.js file in our case)

  1. I hope you are aware of React Lifecycle hooks and basics of react. If you don't, take a pause, understand it and then come back here. So change the app/javascript/components/App.js to below. All code is self-explanatory.
    import React, { Component } from 'react';
    
    class App extends Component {
    
      state = {
        articles: []
      };
    
      componentDidMount() {
        fetch('http://192.168.163.132:3002/articles')
          .then((res) => res.json())
          .then((response) => {
            this.setState({ articles: response.data });
          })
          .catch((err) => {
            console.log(err);
          });
      }
    
      renderAllPosts = () => {
        return(
          <ul>
            {this.state.articles.map(article => (
              <li key={article.id}><strong>Title: </strong>  {article.title} || <strong>Body: </strong> {article.body}</li>
            ))}
          </ul>
        )
      }
    
      render() {
        return (
          <div>
            <h1>Hello world from App.js</h1>
            <h3>All Articles</h3>
            { this.renderAllPosts() }
          </div>
        );
      }
    }
    
    export default App;
    

That's it, restart the server, and you'll see all the articles listed once the webpage loads completely.

The End

Image by Colleen O'Del from Pixabay

Let's recap:

  • We first learned why using React and Rails together makes our workflow easy and fast.
  • Then we learned about prerequisites.
  • We then created a new rails app with React which is used by web packer.
  • We added react-router-dom package, configured our app/javascript folder to work properly.
  • We created the pages controller and added required routes. This page will serve as a SPA for our react app. Also, we configured the database.
  • We then worked on building Articles API
  • We then used the Articles API in our React App to show the list of articles using fetch API.