React and Ruby on Rails: Build a Full-Stack CRUD Application
Tags: Heroku, Full Stack, React, Ruby on Rails

This article is for people who are familiar enough with React as well as Ruby on Rails and are looking to combine the two to create a full-stack web application to perform simple CRUD operations. There is also a bonus part to this article apropos to deploying the completed application on Heroku.

Before going any further, make sure you have set up both React and Ruby on Rails on your system. The application is ready to be built now!

Building the project

1. Create a directory

First and foremost, create a directory for your project in a terminal and navigate into that directory.

mkdir book-app

cd book-app

2. Create a new Rails application

Set up a Rails application for the back-end using the following command in the same directory.

rails new . --api --database=postgresql -T

Explanation:

rails new

Creates a new Rails application with the default directory structure and configuration. .

Indicates to create a new Rails application in the current directory. --api

Preconfigures a smaller stack for API-only apps. --database=postgresql

Rails ships with sqlite3 as the default database but we'll be using PostgreSQL since it's known to be more robust. -T

Skips test files i.e., does not include TestUnit.

Now, open the new project with an IDE. I prefer VS Code.

code .

3. Create a new React application

In the same directory, create a React app called client for the front-end either using npm or yarn (only one of the following).

npx create-react-app client (or) yarn create react-app client

4. Set up Proxy for your React app

To have proxy the API calls via the right port in the React app we need some additional configuration.

To do that, install http-proxy-middleware.

yarn add http-proxy-middleware

Now in the src folder inside your client, create a setup file called setupProxy.js for your proxy and add the following code to it.

5. Set up record relationships for the model

The scaffold in API mode gives the elements: migration, model, route, controller.

a. Book

Scaffold a resource called Book using:

rails g scaffold Book title:string author:string genre:string

The creation should look like:

Running via Spring preloader in process 47634

invoke active_record

create db/migrate/20210902131158_create_books.rb

create app/models/book.rb

invoke resource_route

route resources :books

invoke scaffold_controller

create app/controllers/books_controller.rb

invoke resource_route

Now set up the new model called book.rb which will be created inside the app/models folder as you can see above.

b. Item

Similarly, create a resource called Item.

rails g scaffold Item type:integer name:string author:string genre:string url:string

The type is an integer since it will be used as an enum.

Running via Spring preloader in process 47930

invoke active_record

create db/migrate/20210902131800_create_items.rb

create app/models/item.rb

invoke resource_route

route resources :items

invoke scaffold_controller

create app/controllers/items_controller.rb

invoke resource_route

Now, the model.

c. BookItem

Now, to create a join-relationship of has_many :through :

rails g model BookItem book:references item:references genre:string position:integer

References are used to create the index as well as the belongs_to in the model.

This will only create the migration and model like so:

Running via Spring preloader in process 48214

invoke active_record

create db/migrate/20210902132256_create_book_items.rb

create app/models/book_item.rb

The model:

6. Create a database, migrate and seed it

Create the PostgreSQL DB and migrate it using:

rake db:create

rake db:migrate

Now, the seeding. In db/seeds.rb , add the data:

Feel free to modify it as you wish.

Time to seed the data:

rake db:seed

7. Routes and Controllers

Putting all URLs behind an “api” namespace, and versioning our API as “v1” is good practice.

To do that, modify config/routes.rb :

To reflect this namespacing structure in our controllers, create folders api/v1 inside app/controllers . Move the books_controller.rb and items_controller.rb files (generated by the scaffold) from app/controllers to app/controllers/api/v1 . Now, wrap each of the 2 controllers into a module Api::V1 like:

module Api::V1

...

end

The files should look like this:

The application can be run now to check if the data has been seeded and start the API endpoint by asking for the seeded data.

rails s -p 3001

You should be seeing our JSON at http://localhost:3001/api/v1/books now.

Note: Earlier, we set up a proxy for asking all calls beginning with “/api” to reach localhost:3001 (where the Rails API is running), and not :3000 (where the React client is at).

8. Setting up React components

Inside client/src , you should find the App.js file. Import the <BooksContainer /> component.

Now, build the <BooksContainer /> component. Create a folder called components inside the src folder and create a file named BooksContainer.js inside it.

9. Fetch API Data using Axios

Install axios to make the API calls (instead of the original fetch that has a few limitations):

cd client

yarn add axios

Now initialize the state and use axios in the componentDidMount() lifecycle event in the file BooksContainer.js .

Change the component’s render function to iterate through the books from the state and display each of them.

To maintain a neat code structure, group the Book code into a simple component called Book.js that receives data through props.

BooksContainer.js and Book.js should look like this now:

10. CRUD operations

Modify the code the perform Create, Read, Update and Delete operations.

a. Create

Correct our scaffoled controller ( books_controller.rb ) to remove the “location”. You should have

# POST /books

def create

@book = Book.new(book_params) if @book.save

render json: @book, status: :created

else

render json: @book.errors, status: :unprocessable_entity

end

end

Now, add a form to the BooksContainer.js component

<NewBookForm onNewBook={this.addNewBook} />

Create a new file called NewBookForm.js for this component.

Add an addNewBook function to the BooksContainer component.

b. Delete

For the DELETE action, in books_controller.rb :

# DELETE /books/1

def destroy

@book.destroy

if @book.destroy

head :no_content, status: :ok

else

render json: @book.errors, status: :unprocessable_entity

end

Update Book.js for the DELETE action:

Add a removeBook function to the BooksContainer component and don’t forget to bind it.

c. Update

For the DELETE action, in books_controller.rb :



def update

if

render json:

else

render json:

end

end # PATCH/PUT /books/1def updateif @book .update(book_params)render json: @book elserender json: @book .errors, status: :unprocessable_entityendend

Now, add a form to the BooksContainer.js component

<NewBookForm onNewBook={this.addNewBook} />

Create a new file called NewBookForm.js for this component.

Update Book.js for the UPDATE action:

After adding functions for the UPDATE action to the BooksContainer component, the final file should look like this:

Now, the application is done!

Note: This is the point to modify the UI for a better outlook if you wish to.

11. Running the application

The application can be run using the following commands on two different terminals to run both the back-end and the front-end:

rails s -p 3001

yarn start

To make things, easier install the Foreman gem.

gem install foreman

Now, create a file called Procfile.dev in the root of the project and add the following code to it:

web: cd client && PORT=3000 npm start

api: bundle exec rails s -p 3001

Also, create a rake file called start.rake in the lib/tasks folder to run the project with a single rake start command.

Add a package.json for production at the root of the project.

Add Foreman to your gemfile as gem ‘foreman’, ‘~> 0.87.2’ (newest version) like:

group :development, :test do

# Call 'byebug' anywhere in the code to stop execution and get a debugger console

gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]

gem 'foreman', '~> 0.87.2'

end

Run this to update the gems:

bundle install

Now, all you have to do to run the whole application in one command is type this in your terminal inside the project folder: