Setting up a Rails API app and Create React App

Linked Projects:

Calreact
 
Lesson code: 

Backend Rails API - https://github.com/learnetto/calreact/tree/m9l1-rails-api

Frontend React app - https://github.com/learnetto/calreact-frontend/tree/m9l2-port-components

In this module, we will look at how to use React with a Rails API. Instead of using a gem for adding React to our Rails app, we’ll convert our Rails app into an API and talk to it with a separate front-end React app.

We’ll use Create React App to build the React app. Create React App is a library which helps you quickly build React apps without any configuration. It comes bundled with all the basics you need to get started with React so it saves a lot of time, especially when you’re learning.

We first need to install Create React App by running 

npm install -g create-react-app

in the command line.

Then we need to convert our Rails app to an API.

We could start from scratch and create a new API-only app. But for our purpose here, it’s easier to just make a few changes and use our existing app.

Note: we’re first starting off with our appointments app without Devise authentication - i.e. the app we built in module 7 with React router. This is so that we can first build a simple API and then add authentication as the next step in the next lesson. It's a little bit more complex and we'll have to do it using a different gem.

First, let's edit the application.rb config file. 

We'll need to add a line to it

config.api_only = true

so this set the mode of the app to API only. 

If you create an API only Rails app from scratch, you'll notice a few other things are different. For example, instead of 

require 'rails/all'

you'll only require things that you need, but we'll leave this as it is for now.

Next, in the ApplicationController, we'll need to remove
 
protect_from_forgery with: :exception

We can just comment it out:

# protect_from_forgery with: :exception

We'll be managing session information in a separate front-end app and use Devise Token Auth gem for handling authentication in  Rails.

Next, let's change the AppointmentsController, so we don't need to render HTML, we can simply render JSON:

render json: @appointments

If you refresh the page now, you'll see JSON appointment data. 

[screenshot 2:06]

Now we'll use these JSON data from our API in a new React app.

We'll start off with the index action and come back to change the other controller methods later.

One thing to note, we're keeping our app route the same. Typically in a production API, it's best practice to namespace your API routes.  For example, something like:

namespace :api do
  namespace :v1 do
    resources :appointments
  end
end

But for our example app, we'll keep it simple and use the existing route.

Now let's make our new client side app with Create React App.

Note: This app will be completely separate from our Rails app. So we will not put it inside our Rails app directory.

Let's cd out of our Rails app directory 

$ cd ..

and then run our new app.

$ create-react-app calreact-frontend

That'll take a few seconds to run. Now let's cd into the directory

$ cd calreact-frontend

and open the code in our editor

$ subl .

Now let's run the app with 

$ yarn start

This will actually start the app on port 3000 and our API is already running on that port, so let's run our API on a different port, for example, port 3001:

$ rails s -p 3001

So now if I go to localhost:3000, I'll get the default Create React App home page. It says

 Welcome to React 
[screenshot 3:31]

And if I go to localhost:3001, we'll get the Rails API JSON data. 

Our job is to make this React app to talk to our Rails API on port 3001.

Let's open up App.js which is a component file that is displayed on the homepage. 

Note: This is just React code, the only difference is the file extension is .js instead of .jsx.  The syntax highlighting as presented in the video is a bit off because my code editor is not quite set up right.

Let's quickly test fetching our API data. Let's add
 
componentDiDMount() {

}

to App.js. We also need to add jQuery because Create React App doesn't include it by default.

Note: You don't need jQuery to make AJAX call, you could use other libraries like Fetch. But we'll use jQuery here just so that we don't have to change any of our component code.

Let's first install jQuery by running

$ yarn add jquery

With jQuery added, let's import it in App.js:

import jQuery from 'jquery';

and then let's add

jQuery.ajax({
  type: 'GET',
  url: 'http://localhost:3001/appointments'
}).done(data => {
  console.log(data);
});

[screenshot: 5:01-2]

There seems to be a problem. Let me console.log a string to see if componentDidMount is getting called by adding the following line to componentDidMount:

console.log('ajax');

Let's try and display the appointments straight on the page. So let's initialise state in the new component called constructor: 

constructor (props) {
  super(props);
  this.state = {
    appointments: []
  }
}

Let's update the state after the AJAX call:

jQuery.ajax({
  type: 'GET',
  url: 'http://localhost:3001/appointments'
}).done(data => {
  this.setState({appointments: data });
});

And let's console.log the data:

jQuery.ajax({
  type: 'GET',
  url: 'http://localhost:3001/appointments'
}).done(data => {
console.log(data);
  this.setState({appointments: data });

Then let's add it in our .jsx. Let's remove this welcome message: 

<p className="App-intro"> 
   To get started, edit <code>src/App.js</code> and save to reload.
</p>

and replace it with 

<p className="App-intro">
  {this.state.appointments.map(appointment => {
    return(<p>{appointment.title}</p>);
    })
  }
</p>

Actually let me just change this jQuery in the AJAX call to a $:

import $ from 'jquery';

$.ajax({
  type: 'GET',
  url: 'http://localhost:3001/appointments'
}).done(data => {
console.log(data);
  this.setState({appointments: data });
});

The update does not work when you refresh the page because of the typo:

componentDiDMount() {

It should be 

componentDidMount() {

OK, so now we get our console message, but the AJAX call is still failing because of Cross Origin Resource Error.

[screenshot: 6:24]

This is happening because the API is running on a different port to the client app. We need to explicitly enable resource sharing with this port. And we will do that using a gem called Rack CORS Middleware. We'll add it to the Gemfile:

gem 'rack-cors', :require => 'rack/cors'

And then we will run 

$ bundle

Then inside the config/application.rb file, we'll add this bit of config from the documentation.

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*'
    resource '*', :headers => :any, :methods => [:get, :post, :options]
  end
end

This specifies where our server will accept requests from, so '*' means from anywhere. We don't want that, we just want to use it from our React app.

To correct it, let's define our source:

config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'http://localhost:3000'
    resource '*', :headers => :any, :methods => [:get, :post, :patch, :delete, :options]
  end
end


Then restart Rails to ensure everything works. 

It looks like we're getting an error from React On Rails. 

[screenshot: 7:16-7:17]

Let's remove  the config file, we don't need it anymore:

$ rm config/initializers/react_on_rails.rb

Then let's start Rails, then let's refresh the browser again.

Now our data loads properly.

[screenshot: 7:32]

You can ignore the warnings because this is just a test of the API. In the next lesson, we'll use it properly.

Liked this tutorial? Get more like this in your inbox