React + Rails + MongoDB: how to decouple Frontend and Backend using React and Ruby on Rails
Tags: decoupling, rails, react, mongodb
Published: 2020-03-22 17:26:14

---
title: React + Rails + MongoDB: how to decouple Frontend and Backend using React and Ruby on Rails
published: true
date: 2020-02-13 20:32:44 UTC
tags: decoupling,ruby-on-rails,react,mongodb
canonical_url: https://medium.com/@bravemaster619/react-ror-mongodb-how-to-decouple-frontend-and-backend-using-react-and-ruby-on-rails-fc6adf40376b
---

 **_Summary:_** _In this article, I am going to explain how to build a React web page using Ruby on Rails for backend and MongoDB for database._  
**_TLDR:_** _Github repository link:_ [https://github.com/bravemaster619/rails-react](https://github.com/bravemaster619/rails-react)

Decoupled structure has many great advantages. Frontend developers don’t need to know what framework and database they are using in backend, as long as they have a decent API documentation.

Backend developers can solely focus on performance and can build a more stable, reliable and bug-free framework. They will get errors only from backend, not from templates! What if the project got bigger and you need to optimize backend for better performace? The backend team can work on API v2 while the website is still live. Once the new API is completed, just change api url from v1 to v2 will do the work! The same thing can be said for frontend optimization and layout changes.

Full stack developers can benefit from decoupled structure, too. Clients often give you designs and flow diagrams and order you to build them from scratch. You can start building frontend pages with mockup data. Once you get to know the main flow and details of the project, it will be much more easier to design database and framework.

In this article, I will show you how to build a decoupled web app using React and Ruby on Rails. I chose MongoDB for database because NoSQL databases are so good for projects with flexible data structures.

#### Prerequisites:

- Latest [Ruby](https://www.ruby-lang.org/en/documentation/installation/) and [Ruby on Rails](https://www.ruby-lang.org/en/documentation/installation/)
- Latest [MongoDB Community Edition](https://docs.mongodb.com/manual/administration/install-community/)
- Latest [Node.js LTS version](https://nodejs.org/en/download/)

### 1. Build a Ruby on Rails Framework

#### Create a directory named rails-react

```console
$ mkdir rails-react  
$ cd rails-react
```

#### Create a new Rails app named backend

```console
$ rails new backend --api --skip-active-record
```

`apiswitch` will optimize and remove middlewares and resources from our Rails app. (To find out more, refer to this link: [_Using Rails for API-only Applications_](https://guides.rubyonrails.org/api_app.html))  
`skip-active-recordswitch` removes ActiveRecord dependency because we won’t need that. We will be using mongoid ([_Click here for official Mongoid Manual_](https://docs.mongodb.com/mongoid/master/tutorials/getting-started-rails/)).

#### Install gem dependencies

Add the following lines before group :development to Gemfile:

```ruby
# mongoid for MongoDB
gem 'mongoid', '~> 7.0.5'

# cors policy
gem 'rack-cors'
```

[mongoid](https://docs.mongodb.com/mongoid/current/) is the official ODM for MongoDB in Ruby. [rack-cors](https://github.com/cyu/rack-cors) makes cross-origin ajax request possible in our project. Since backend and frontend may run in different port, we need to set cors policy in our Rails app.

Next, we are going to install dependencies:

```console
$ bundle install
```

#### Configuration for rack-cors

Add the following lines to `config/application.rb`:

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

It allows Rails app to allow requests from any origins with any methods.

#### Configuration for mongoid

Run the following command in shell:

```console
$ cd bin  
$ rails g mongoid:config
```

It will create a file mongoid.yml in config directory. You can set a database name, hosts and other options for MongoDB. Default database name is `backend_development` and the database host is localhost:27017.

#### Create model and controller

Let’s create a user model. It will have only two fields: name and email.  
To make things easier, we will use scaffold, rather than creating a model and a controller individually.

```console
$ rails generate scaffold User name:string email:string
```

Scaffold will generate migration, model, controller, test suite and routes for a given structure.

After everything is set, you can use the following shell command to run a test server:

```console
$ rails server
```

**_Note:_** _Don’t forget to run mongod before initializing a test server._

### 2. Build a React App

Now you can start building a React app for frontend.

In our project root directory rails-react, run the following command:

```console
$ npx create-react-app frontend
```

#### Install node modules

After installing is done, let’s add react-bootstrap for responsibility and smart looks:

```console
$ cd frontend
$ npm i react-bootstrap bootstrap
```

Add react-toastify for alerts in our app:

```console
npm i react-toastify
```

Since the frontend has to send AJAX requests to Rails API, we need [Axios](https://github.com/axios/axios)s:

```console
$ npm i axios
```

Create a file named `config.js` in `src` directory and add the following code:

```javascript
export const API_HOST = 'http://localhost:3000'
```

#### App.js

Modify App.js like the following:

```javascript
import React from 'react';
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'react-toastify/dist/ReactToastify.css';
import Root from "./components/Root"
import {ToastContainer} from "react-toastify";
const App = () => (
    <>
        <Root />
        <ToastContainer/>
    </>
)
export default App
```

#### Root.jsx

In directory `src`, create a new directory named `components` and make a new file `Root.jsx`. Cut and paste the following code:

```javascript
import React from "react"
import Axios from "axios"
import { alertService } from '../services/alert'
import SubscribeForm from "./SubscribeForm"
import UserTable from "./UserTable"
import { API_HOST } from "../config"

class Root extends React.Component {

    constructor(props) {
        super(props)
        this.state = {
            name: '',
            email: '',
            sendingRequest: false,
            subscription: false,
        }
        this.changeName = this.changeName.bind(this)
        this.changeEmail = this.changeEmail.bind(this)
        this.subscribe = this.subscribe.bind(this)
    }

    changeName(e) {
        let name = e.target.value
        this.setState({name})
    }

    changeEmail(e) {
        let email = e.target.value
        this.setState({email})
    }

    subscribe() {
        this.setState({
            sendingRequest: true
        })
        if (!this.state.name) {
            return alertService.showError('Please input name!')
        }
        if (!this.state.email) {
            return alertService.showError('Please input email!')
        }
        Axios.post(`${API_HOST}/users`, {
            name: this.state.name,
            email: this.state.email,
        }).then(res => {
            if (res.data && res.data._id) {
                this.setState({
                    subscription: true
                })
            } else {
                alertService.showError('Subscription failed!')
            }
        }).finally(() => {
            this.setState({
                sendingRequest: false
            })
        })
        
    }

    render() {
        return (
            <div className="container">
                {this.state.subscription ? (
                    <UserTable
                        subscription={this.state.subscription}
                    />
                ) : (
                    <SubscribeForm
                        name={this.state.name}
                        email={this.state.email}
                        changeName={this.changeName}
                        changeEmail={this.changeEmail}
                        subscribe={this.subscribe}
                        sendingRequest={this.state.sendingRequest}
                    />
                )}
            </div>
        )
    }

}

export default Root
```

#### alert.jsx

In `src` directory, create a new directory called services. Then create a new file `alert.jsx`. Cut and paste the following code:

```javascript
import React from 'react';
import { toast } from 'react-toastify'

class AlertService {
    showSuccess(title, content = '') {
        toast.success(<div dangerouslySetInnerHTML={{ __html : title + '<br/>' + content }}></div>);
    }

    showError(title, content = '') {
        toast.error(<div dangerouslySetInnerHTML={{ __html : title + '<br/>' + content }}></div>);
    }
}

export const alertService = new AlertService();
```

#### SubscribeForm.jsx

In `components` directory, create a new file `SubscribeForm.jsx`:

```javascript
import React from "react"

class SubscribeForm extends React.Component {

    constructor(props) {
        super(props)
    }

    render() {
        return (
            <div className="row mt-5 justify-content-center">
                <div className="col-12 col-lg-6 border border-1 p-4">
                    <form className="">
                        <div className="form-group">
                            <label className="col-form-label">Name</label>
                            <input
                                className="form-control"
                                type="text"
                                placeholder="Please input your name"
                                value={this.props.name}
                                onChange={this.props.changeName}/>
                        </div>
                        <div className="form-group">
                            <label className="col-form-label">Email</label>
                            <input
                                className="form-control"
                                type="text"
                                placeholder="Please input your email"
                                value={this.props.email}
                                onChange={this.props.changeEmail}/>
                        </div>
                        <hr className="my-4"/>
                        <div className="form-group text-right">
                            {this.props.sendingRequest ? (
                                <button type="button" className="btn btn-primary" disabled>Sending Request...</button>
                            ) : (
                                <button type="button" onClick={this.props.subscribe}
                                        className="btn btn-primary">Subscribe</button>
                            )}
                        </div>
                    </form>
                </div>
            </div>
        )
    }

}

export default SubscribeForm
```

#### UserTable.jsx

In components directory, create a new file `UserTable.jsx`:

```javascript
import React from "react"
import { alertService } from '../services/alert'
import Axios from "axios"
import { API_HOST } from "../config"
class UserTable extends React.Component {

    constructor(props) {
        super(props)
        this.state={
            loading: true,
            users: []
        }
    }

    componentDidMount() {
        Axios.get(`${API_HOST}/users`).then(res => {
            this.setState({
                users: res.data
            })
        }).catch(e => {
            alertService.showError('Cannot get user data...')
        }).finally(() => {
            this.setState({
                loading: false
            })
        })
    }

    render() {
        return (
            <div className="row mt-5 justify-content-center">
                <div className="col-12 col-lg-8">
                    <table className="table table-hover table-striped">
                        <thead>
                            <tr>
                                <th>No</th>
                                <th>Name</th>
                                <th>Email</th>
                            </tr>
                        </thead>
                        <tbody>
                        {this.state.loading ? (
                            <tr><td>Loading...</td></tr>
                        ) : (
                            <>
                                {this.state.users.map((user, index) => {
                                    return (
                                        <tr key={index}>
                                            <thd>{index+1}</thd>
                                            <td>{user.name}</td>
                                            <td>{user.email}</td>
                                        </tr>
                                    )
                                })}
                                {!this.state.users.length && (
                                    <tr><td>Loading...</td></tr>
                                )}
                            </>
                        )}
                        </tbody>
                    </table>
                </div>
            </div>
        )
    }

}

export default UserTable
```

### 3. Let’s check it out!

First of all, ensure that [MongoDB is up and running](https://docs.mongodb.com/manual/tutorial/manage-mongodb-processes/).

![](https://cdn-images-1.medium.com/max/962/1*QrhFRXFEzbqVIrcxT_Xh3A.png)

Next, run Rails server for backend:

```console
$ cd bin 
$ rails server
```

![](https://cdn-images-1.medium.com/max/567/1*Iie1yT7j6q8Vxwcry6w5Sw.png)

And then run React app:

```console
$ npm run start
```

![](https://cdn-images-1.medium.com/max/561/1*qeXJGPmpCg7c2wVNm_yLiQ.png)

Unfortunately, port 3000 is default port for both Rails and React. You can set a [different port for Rails using -p switch](https://guides.rubyonrails.org/kindle/command_line.html#rails-server) or [modify package.json to run React app on a diffrent port](https://stackoverflow.com/questions/40714583/how-to-specify-a-port-to-run-a-create-react-app-based-project). But simply you can say Y to let our React run on port 3001.

![](https://cdn-images-1.medium.com/max/618/1*n0sK2qWqlN6slItPEqZJVA.png)

When you fill the form and click Subscribe button, the page will show you a table of all subscribed users.

![](https://cdn-images-1.medium.com/max/763/1*aV_y_FID0C_ojB46il-6hQ.png)

### 4. How does this work?

When a user input name and email and click “Subscribe”, Axios will send a POST request to Rails server.

```javascript
Axios.post(`${API_HOST}/users`, {
    name: this.state.name,
    email: this.state.email,
})
```

Since we created User model by scaffold, REST Api routes for user are already set in Rails server - in `config/routes.rb`:

```ruby
Rails.application.routes.draw do
  resources :users
end
```

POST requests are handled in `users#create`:

```ruby
# POST /users
def create
  @user = User.new(user_params)

  if @user.save
    render json: @user, status: :created, location: @user
  else
    render json: @user.errors, status: :unprocessable_entity
  end
end
```

Since our Rails App works as API, the `users_controller` will respond with JSON instead of rendering erb files.

Axios will receive the JSON result and check if there is BSONObjectId to see the user is created successfully.

```javascript
Axios.post(`${API_HOST}/users`, {
    name: this.state.name,
    email: this.state.email,
}).then(res => {
    if (res.data && res.data._id) {
        this.setState({
            subscription: true
        })
    } else {
        alertService.showError('Subscription failed!')
    }
})
```

If the user is successfully created, it will update subscription state as true. Then the Root component will render `UserTable` component.

After `UserTable` component is mounted, it sends GET request to API, which will return JSON Array of all users stored in MongoDB:

```javascript
componentDidMount() {
    Axios.get(`${API_HOST}/users`).then(res => {
      this.setState({
        users: res.data
      })
    }).catch(e => {
        alertService.showError('Cannot get user data...')  
    }).finally(() => {
        this.setState({
          loading: false
        })
    })
}
```

If the result is successful, it will update users state and show all users stored in database:

![](https://cdn-images-1.medium.com/proxy/1*aV_y_FID0C_ojB46il-6hQ.png)

Here is an image of [MongoDB Compass](https://www.mongodb.com/products/compass) showing stored users:

![](https://cdn-images-1.medium.com/max/1024/1*xyNjPG6Bx8F7B0y9Ql7c0A.png)

### Useful Links

- You can see and download the working repository here: [https://github.com/bravemaster619/rails-react](https://github.com/bravemaster619/rails-react)
- [In my next article](https://medium.com/@bravemaster619/how-to-convert-a-react-ror-web-app-to-pwa-push-notification-using-vapid-ee33aba90f8a), I will show you how to convert this web app into PWA and show web push notifications.