Christian Carey
Christian Carey

Reputation: 31

How do I deploy a React app and a Rails API to Heroku?

I'm working on an app that uses React as the client with a Rails API as the server. I want to keep the client and server as separate as possible (right now they are in totally different repositories).

The React client was created using create-react-app, and currently calls out to an API using the proxy option in the package.json file, like so:

"proxy": "http://localhost:3001/"

Localhost 3001 is the location of my Rails API. Obviously this will not work in production.

How can I deploy this to Heroku while still letting the client have access to the server? Can I deploy two different Heroku apps and somehow have the client's app proxy requests out to the server's app? Or do I have to include the client inside of my rails project and deploy it all together?

Upvotes: 2

Views: 1770

Answers (2)

quicklikerabbit
quicklikerabbit

Reputation: 3556

I took a different approach than Jimmy in doing this same thing.

I deployed two apps on Heroku: a Rails API and a Create-React-App front end. Without getting too specific, there are a few keys for setting this up. First, in your rails api, edit the cors.rb file so that it allows cross-origin requests:

Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'localhost:3000', 'https://myapp.herokuapp.com'

    resource '*',
    headers: :any,
    methods: [:get, :post, :put, :delete],
  end
end

As this file implies, my rails app does not run on localhost:3000 locally, i changed it to run on port 8000 instead by editing puma.rb:

port        ENV.fetch("PORT") { 8000 }

Create-react-app runs on localhost:3000 locally by default, so you can set your rails api to run on whatever port that you want, as long as it's different than your front-end.

I then made a file in my create-react-app that contains the API url and commonly used endpoints that I call AppConstants.js:

// let APIRoot = "http://localhost:8000";
let APIRoot = "https://my-rails-api.herokuapp.com";

export default {
  APIEndpoints: {
    LOGIN:  APIRoot + "/users/login",
    SIGNUP: APIRoot + "/users/create",
    TODOS: APIRoot + "/todos",
    ITEMS: APIRoot + "/items",
  },
};

Now you edit your fetch/ajax/xmlHttpRequest calls so that the URL it uses references these routes. For example using fetch:

fetch(AppConstants.TODOS, {
  method: 'POST',
  body: JSON.stringify(values)
})
.then(response => response.text())
.then((body) => {
  console.log(body);
});

This app constants file makes it easy to switch between a local api root and a production api root.

Deploy your rails api to heroku as you normally would, a suitable build pack will automatically be detected. For your react app, I suggest using this buildpack as will make a production build of your create-react-app and serve the static assets for you.

Upvotes: 1

Jimmy Baker
Jimmy Baker

Reputation: 3255

I've done this before. I started out as you suggested in your question by building the react app and including it in my rails project and deploying the whole thing. This works just fine but I didn't feel like redeploying my back-end api every time I wanted to deploy my front-end app.

So I made a model called FrontEndAppVersion in my rails app. I gave this model two columns:

  1. version - A semantic version string that you normally see
  2. active - a boolean value indicating if it was the active version (only one record could be active at a time)

I then had a minimal rails view render which would look for the active FrontEndAppVersion and would link to the appropriate js and css files.

Over in my react app I used gulp to package up the app and push it to cloud front which is where my rails app linked to. In addition to sending the react files to cloud front, I had it make an api request to my rails backend app to let it know that there was a new version available and to make it the active one.

This might be overkill, but it also allowed me to rollback a version with ease. Instead of rolling back Heroku, I could rollback just the client side app by making a different FrontEndAppVersion record the active one.

Either way you go, the proxy will need to change when you build it and if both apps are on the same domain, you can simply use /.

Upvotes: 1

Related Questions