๐ฑ A Rails 6 API backend React JS + Webpacker guide. Includes API setup, serializers, and react integration in the frontend. ๐ฒ
Hey! This is a super-easy to follow Rails/React API tutorial that is fully in depth from start to finish. This guide shows you how to install a Ruby on Rails 6 API with React JS via Webpacker and connecting the frontend with the backend.
Stack: Rails 6 API + React JS located in app/javascript
+ Webpacker
ruby "2.6.3" +
gem "rails", "~> 6.1.3"
The new rails api command scaffolds everything we need to get up and ready for our project. Let's start our rails server and being integrating the backend first.
rails new my_app -T --database=postgresql
What's going on here? The -T
command also tells rails that we don't want Minitest as our testing suite. You'll most likely be used to Rspec so we'll talk about that later in the guide. The --database=postgresql
line is pretty much self explanatory!
Versioning is the process of seperating and creating new features/data/endpoints for your API. Since this is our first API, let's make our rails api v1.
mkdir app/controllers/api && mkdir app/controllers/api/v1
Now that our versioning is complete, let's test out a model and controller to work with our new url of localhost:3000/api/v1
.
movies
rails g scaffold Movies name:string rating:integer
Don't forget to rails db:create
if it was not yet initialized!
Then we can use rails db:migrate
for our scaffold.
The Rails engine creates your controller in the default /controllers
directory but we need to move our new controller into the api/v1
directory.
mv app/controllers/movies_controller.rb app/controllers/api/v1
Our newly generated controller does not properly inherit from the namespace api/v1 (We will update the routes later in the tutorial) so let's change our controller class from
class MoviesController < ApplicationController
TO
module Api
module V1
class MoviesController < ApplicationController
# The scaffold ruby code is here~
end
end
end
This makes it so we can INHERIT from the application controller without any additional tinkering.
routes.rb
file.Rails.application.routes.draw do
resources :movies
end
If we go to localhost:3000/movies
we will not call the controller. We must update our Routes to:
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :movies
end
end
end
which allows us to call the json data from localhost:3000/api/v1/movies
Copy and paste the following data to your db/seeds.rb
file.
Movie.create(name: "The Nightmare Before Christmas", rating: 5)
Movie.create(name: "Titanic", rating: 5)
Movie.create(name: "Venom", rating: 4)
Movie.create(name: "A Quiet Place", rating: 5)
Movie.create(name: "Nobody's Fool", rating: 2)
Movie.create(name: "Suspiria", rating: 4)
Movie.create(name: "Hereditary", rating: 4)
Movie.create(name: "Office Space", rating: 5)
Movie.create(name: "Elf", rating: 4)
Movie.create(name: "Dawn of the Planet of the Apes", rating: 3)
Movie.create(name: "Secret life of Pets", rating: 4)
Movie.create(name: "Overlord", rating: 3)
Movie.create(name: "Wonder Woman", rating: 5)
Movie.create(name: "Bohemian Rhapsody", rating: 4)
Movie.create(name: "Ocean's 8", rating: 5)
Seed the DB using rails db:seed
.
Start your Rails server rails s
or rails s -b 0.0.0.0
and navigate to localhost:3000/api/v1/movies
and if it is successful you should see the following JSON output:
(Optional) I'm using a pretty JSON viewer for chrome which you can download.
Congrats! You have successfully created a Rails API and completed your first GET request!
React is a component based front end framework that makes it easy to make frontend calls to our Rails API. Let's make this organized as possible and add our react directory inside our rails app/javascript
directory.
bundle exec rails webpacker:install:react
And wait for the Webpacker now supports react.js ๐
message to finally use React! Woah :)
Note: Notice the H1 tag on the Hello {props.name}!
, this is so we can see it clearly. When intalling, the H1 tag will not be there. It is purely cosmetic for now.
# app/javascript/packs/hello_react.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
const Hello = props => (
<div><h1>Hello {props.name}!</h1></div>
)
Hello.defaultProps = {
name: 'David'
}
Hello.propTypes = {
name: PropTypes.string
}
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<Hello name="React" />,
document.body.appendChild(document.createElement('div')),
)
})
We can hook this file into any view we want React to send, so lets try this out now and add this to a demo view.
rails g controller Home index
and locate your routes in config/routes.rb
Open the route file and ROOT your application to the home controller
Rails.application.routes.draw do
root 'home#index'
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end
hello_react.jsx
file to the HOME view, so let's include that now.<%= javascript_pack_tag "hello_react" %>
React has been linked, successfully.
Personally, I don't like the structure of React in this concept, so let's add something more "neater" so we can find our components easier.
components
and packs
inside the javascript folder.packs
folder, create a file named application.js
and add the following to it:import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
Rails.start()
Turbolinks.start()
ActiveStorage.start()
Let's also create a hello_react.jsx file and add the following:
import React from 'react'
import ReactDOM from 'react-dom'
import App from '../components/App'
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<App />,
document.body.appendChild(document.createElement('div')),
)
})
components
and create a file named App.js
Add the following to App.js
import React, { Component } from 'react'
class App extends Component {
render(){
return(
<div>
<h1> React says Hello! </h1>
</div>
)
}
}
export default App
components
folder and add any new component you wish!. Let's run the rails server to see if its calling our new H1 text.React JS is all about components! Assuming we know basic React workflow, lets create a new component under the component
direct named MovieInfo.js
MovieInfo.js
as the basic skeleton.# app/js/components/MovieInfo.js
import React from 'react'
export class MovieInfo extends React.Component {
constructor() {
super();
}
render() {
return (
<div>
</div>
)
}
}
export default MovieInfo;
constructor() {
super();
this.state = {
movies: []
};
}
componentDidMount
function so the data can be created in the virtual DOM on page load. componentDidMount(){
fetch("/api/v1/movies")
.then(resp => resp.json())
.then(m => {
this.setState({
movies: m
})
})
.catch(error => console.log(error))
}
import React from 'react'
export class MovieInfo extends React.Component {
constructor() {
super();
this.state = {
movies: []
};
}
componentDidMount(){
fetch("/api/v1/movies")
.then(resp => resp.json())
.then(a => {
this.setState({
movies: a
})
})
.catch(error => console.log(error))
}
render() {
return (
<div>
{this.state.movies.map(obj =>
<p key={obj.id}>{obj.name}</p>
)}
</div>
)
}
}
export default MovieInfo;
App.js
file.import React, { Component } from 'react'
// Here V
import MovieInfo from './MovieInfo'.
class App extends Component {
render(){
return(
<div>
// And Here V
<MovieInfo />
</div>
)
}
}
export default App
๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐๐ Congratulations! Our Rails API and React Client is done! If you enjoyed this API tutorial please give it a star and share it with your friends!
What are Serializers? Well Rails API's returns JSON data in full, so serializers allows us to cherry pick the exact data we want in a much organized fashion. Instead of getting every column from the returned data, we can grab which ever we allow to pass through.
Normal Model | Serializer Model |
---|---|
id, name, rating, director, score, actors_id, created_at, updated_at | id, name, rating |
We are able to tell the Rails API what to fetch rather than the frontend; making it much more simple and faster to scaffold your next project.
Open your gemfile
and add the serializer gem into your project. Don't forget to bundle install
!
# Serializer
gem 'active_model_serializers', '~> 0.10.0'
We want to create a clone of any current model we have so when we make requests in the backend, the request will read the serializer file first, then it will find the rails model/controller to finisht the request. We have a model called Movie so we'll duplicate that by running:
rails g serializer movie
You can see that a new directory was made in the app/
directory and we now have app/serializers/movie_serializer
file in our project.
Let's open that file and see what we have:
class MovieSerializer < ActiveModel::Serializer
attributes :id, :name, :rating
end
We have our Movie Class inheriting from the serializer class on the first line, and the returned attribute on the second. So far the default returned attribute is just an ID, a name, and a rating.
1a. Turn on your rails server and go to the url localhost:3000/api/v1/movies
You should see that only the id
and name
attribute is being returned from the database.
{ "movies":
[
{
"id": 1,
"name": "The Nightmare Before Christmas",
"rating": 5
},
{
"id": 2,
"name": "The Titanic",
"rating": 5
},
{
"id": 3,
"name": "Venom",
"rating": 4
}
]
}
We are done! What is the takeaway?
React will be fetching a lot of JSON data and we can reduce the uneeded attributes like created_at
or updated_at
that does not need to be displayed on the page.
I love collaboration! Please feel free to contribute or add your insights โจ
John Cordero ยฉ johncorderox, Released under the MIT License.
Blog @jc ยท GitHub @johncorderox ยท