A Rock Solid, Modern Web Stack—Rails 5 API + ActiveAdmin + Create React App on Heroku
Tags: rails, api, react, activeadmin
Published: 2018-05-17 15:23:37

---
title: A Rock Solid, Modern Web Stack—Rails 5 API + ActiveAdmin + Create React App on Heroku
description: How to blend a rock-solid CMS and API with the absolute best in front-end tooling, built as a single project and hosted seamlessly on Heroku.
published: true
tags: rails, api, react, ActiveAdmin
canonical_url: https://blog.heroku.com/a-rock-solid-modern-web-stack
---

 **How to blend a rock-solid CMS and API with the absolute best in front-end tooling, built as a single project and hosted seamlessly on Heroku.**

[Rails](http://rubyonrails.org/) is an incredible framework, but modern web development has moved to the front-end, meaning sometimes you don’t need all the bulk of the asset pipeline and the templating system. In Rails 5 you can now create an API-only Rails app, meaning you can build your front-end however you like—using Create React App, for example. [It’s no longer 100% omakase](http://david.heinemeierhansson.com/2012/rails-is-omakase.html).

![An image of four logos, React, Rails, Activeadmin, and Heroku](https://heroku-blog-files.s3.amazonaws.com/posts/1526402185-A%20Rock%20Solid%20Stack.jpg)

<!-- more -->

And for projects that don’t need CMS-like capabilities, Rails and that works pretty great straight away. Create React App even supports [proxying API requests in development](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#proxying-api-requests-in-development), so you can be running two servers locally without having to litter your app with `if NODE_ENV === ‘development’`.

Still, I’ve worked with [ActiveAdmin](https://github.com/activeadmin/activeadmin) on a few projects, and as an interface between you and the database, it’s pretty unmatched for ease of use. There are a host of customisation options, and it’s pretty easy for clients to use if you need a CMS. The issue is that removing the non-API bits of Rails breaks it. Not ideal. But all is not lost—with a couple of steps you can be running a Rails 5 app, API-only, serving your Create React App client on the front end, with full access to ActiveAdmin.

We’re going to build it, then we’re going to [deploy it to Heroku](https://list-of-ingredients.herokuapp.com/), and then we’re going to celebrate with a delicious, healthy beverage of your choosing. Because we will have earned it. And given that theme, we’re going to build an app that shows us recipes for smoothies. It’s thematically appropriate!

So, what are we going to use?

- **[Create React App](https://github.com/facebookincubator/create-react-app)**
All the power of a highly-tuned Webpack config without the hassle.


- **[Rails in API-only mode](http://edgeguides.rubyonrails.org/api_app.html)**
Just the best bits, leaving React to handle the UI.


- **[ActiveAdmin](https://github.com/activeadmin/activeadmin)**
An instant CMS backend.


- **Seamless deployment on [Heroku](https://heroku.com/)**
Same-origin (so no CORS complications) with build steps to manage both Node and Ruby.


- **Single page app support with [React Router](https://github.com/ReactTraining/react-router)**
So you can have lightning fast rendering on the front end.

And it’ll look something like this:

![An image of the app we'll be building, showing recipes for two drinks](https://heroku-blog-files.s3.amazonaws.com/posts/1526300250-site-initial.jpg)<figcaption>Our app, <a href="https://list-of-ingredients.herokuapp.com/">List of Ingredients</a>, which really does what it says on the tin.</figcaption>

If you want to skip ahead to the finished repo, [you can do so here](https://github.com/heroku/list-of-ingredients), and if you want to see it in action, [you do that here](https://list-of-ingredients.herokuapp.com/).

Let’s get started, shall we?

* * *

## Step 1: Getting Rails 5 set up

### With that delicious low-carb API-only mode

* * *

There are a ton of great tutorials on getting Ruby and Rails set up in your local development environment. [https://gorails.com/setup/](https://gorails.com/setup/) will work out your operating system, and will walk you through getting Rails 5.2.0 installed.

If you’ve already got Rails 5, awesome. The best way to check that is to run `rails -v` in your terminal. If you see `Rails 5.2.0`, we’re ready to roll.

So, first up, start a new Rails app with the `--api` flag:

```
mkdir list-of-ingredients
cd list-of-ingredients
rails new . --api
```

Before you commit, add `/public` to `.gitignore`, as this will be populated at build by our front end. Your `.gitignore` file should look something like this:

```
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'

# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal

# Ignore all logfiles and tempfiles.
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep

# Ignore uploaded files in development
/storage/*

.byebug_history

# Ignore master key for decrypting credentials and more.
/config/master.key

# Ignore public, as it is built on deploy
# Place files for /public in /client/public
/public
```

Right. We are already part of the way to making a delicious smoothie. Maybe use this time to congratulate yourself, because you’re doing great.

Once the install process has finished, you can fire up Rails:

```
bin/rails s -p 3001
```

It’ll do some stuff, eventually telling you that it’s listening on `http://localhost:3001`. If you visit it, you should see something like this:

![The initial Rails page, showing version 5.2.0](https://heroku-blog-files.s3.amazonaws.com/posts/1526300434-rails.jpg)<figcaption>Yay Rails!</figcaption>

Look—there’s even a _kitten_ in that illustration! So great. Let's quit Rails and get ready for step 2.

* * *

## Step 2: Getting ActiveAdmin working

### With a couple of small tweaks to Rails

* * *

([Thanks to Roman Rott for inspiring this bit.](https://rrott.com/blog/ror/rails-5-api-with-activeadmin-integration.html))

So, why do we need to make any changes at all to get Rails up and running? It's because when we make a Rails API app, Rails isn't expecting to serve HTML pages, and because we're adding [ActiveAdmin](https://github.com/activeadmin/activeadmin), we actually need it to.

Before you install ActiveAdmin, you'll need to switch a couple of Rails classes and add some middleware that it relies on.

First, you’ll need to swap your `app/controllers/application_controller.rb` from using the `API` to using `Base`, being sure to add in `protect_from_forgery with: :exception`.

So your `application_controller.rb` should go from looking like this:

```ruby
class ApplicationController < ActionController::API
end
```

To something more like this:

```ruby
class ApplicationController < ActionController::Base
    protect_from_forgery with: :exception
end
```

As [Carlos Ramirez mentions](http://www.carlosramireziii.com/how-to-add-active-admin-to-a-rails-5-api-application.html), this requirement is due to a design decision from ActiveAdmin, meaning any controllers we make that inherit from `ApplicationController` won’t take advantage of the slimmed down API version.

There is a work around, though. Add a new `api_controller.rb` file to your `app/controllers`:

```ruby
class ApiController < ActionController::API
end
```

Now you can get any new controllers you make to inherit from `ApiController`, not `ApplicationController`. For example, if you were making an `ExampleController`, it might look like this:

```ruby
class ExampleController < ApiController
end
```

From there we’ll need to ensure that the middleware has the stuff it needs for ActiveAdmin to function correctly. API mode strips out cookies and the flash, but we can 100% put them back. In your `config/application.rb` add these to the `Application` class:

```ruby
# Middleware for ActiveAdmin
config.middleware.use Rack::MethodOverride
config.middleware.use ActionDispatch::Flash
config.middleware.use ActionDispatch::Cookies
config.middleware.use ActionDispatch::Session::CookieStore
```

You’ll also need to add `sprockets/railtie` back in by uncommenting it:

```ruby
require "sprockets/railtie"
```

Your `config/application.rb` should look something like this:

```ruby
require_relative 'boot'

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"
require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module ListOfIngredients
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.api_only = true

    # Middleware for ActiveAdmin
    config.middleware.use Rack::MethodOverride
    config.middleware.use ActionDispatch::Flash
    config.middleware.use ActionDispatch::Cookies
    config.middleware.use ActionDispatch::Session::CookieStore
  end
end
```

Next up, your `Gemfile`. You’ll need to add the ActiveAdmin gems in:

```ruby
# ActiveAdmin
gem 'devise'
gem 'activeadmin'
```

You should also move `gem 'sqlite3'` into the `:development, :test` group and add `gem 'pg'` into a new `:production` group. This is because Heroku doesn’t support sqlite's local disk storage (see [factor six](https://12factor.net/processes) in The Twelve-Factor App), so you’ll need to ensure you're using Postgres for production.

```ruby
group :development, :test do
  # Use sqlite3 as the database for Active Record
  gem 'sqlite3'
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :production do
  # Use postgres as the database for production
  gem 'pg'
end
```

Your Gemfile should now look something like this:

```ruby
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.5.1'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.0'
# Use Puma as the app server
gem 'puma', '~> 3.11'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
# gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8'

# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false

# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

group :development, :test do
  # Use sqlite3 as the database for Active Record
  gem 'sqlite3'
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  gem 'listen', '>= 3.0.5', '< 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :production do
  # Use postgres as the database for production
  gem 'pg'
end

# ActiveAdmin
gem 'devise'
gem 'activeadmin'

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
```

Okay, okay. Someone out there will probably be sharpening their pitchfork right now because you should 100% run Postgres locally if you’re developing a Real Application to ensure your local environment matches your production one. But to make this tutorial a little less verbose, we’re going to bend the rules, together.

Bundle install everything, and then install ActiveAdmin into your Rails app:

```
bundle
bin/rails g active_admin:install
```

You should see something like the following:

```
Running via Spring preloader in process 57692
      invoke devise
    generate devise:install
      create config/initializers/devise.rb
      create config/locales/devise.en.yml
  ===============================================================================

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

===============================================================================
      invoke active_record
      create db/migrate/20180501170855_devise_create_admin_users.rb
      create app/models/admin_user.rb
      invoke test_unit
      create test/models/admin_user_test.rb
      create test/fixtures/admin_users.yml
      insert app/models/admin_user.rb
       route devise_for :admin_users
        gsub app/models/admin_user.rb
        gsub config/routes.rb
      append db/seeds.rb
      create config/initializers/active_admin.rb
      create app/admin
      create app/admin/dashboard.rb
      create app/admin/admin_users.rb
      insert config/routes.rb
    generate active_admin:assets
Running via Spring preloader in process 57711
      create app/assets/javascripts/active_admin.js
      create app/assets/stylesheets/active_admin.scss
      create db/migrate/20180501170858_create_active_admin_comments.rb
```

Finally, migrate and seed the database:

```
bin/rake db:migrate db:seed
```

Once again you can fire up Rails:

```
bin/rails s -p 3001
```

This time hit `http://localhost:3001/admin`. You should see something like this:

![The ActiveAdmin login page](https://heroku-blog-files.s3.amazonaws.com/posts/1526300515-admin.jpg)

And you should take a moment to feel pretty great, because that was _a lot_.

You can log into ActiveAdmin with the username `admin@example.com` and the password `password`. Security! You can change it really easily in the rad ActiveAdmin environment, though, so fear not.

* * *

## Step 3: Adding Create React App as the client

### Yay! Super-speedy Webpack asset handling!

* * *

([Shout out to Full Stack React for inspiring this bit.](https://www.fullstackreact.com/articles/how-to-get-create-react-app-to-work-with-your-rails-api/))

So. We need a front end. If you don’t have Create React App yet, install it globally with:

```
npx create-react-app client
```

`npx` comes with npm 5.2+ and higher. If you’re using an older version, you can run:

```
npm install -g create-react-app
create-react-app client
```

It’ll take a bit. You probably have time for a cup of tea, if you’re feeling thirsty.

Once it’s done, jump into `client/index.js` and remove these two lines:

```javascript
import registerServiceWorker from './registerServiceWorker';
registerServiceWorker();
```

This is because, in some cases, Create React App’s use of service workers clashes with Rails’ routing, and can leave you unable to access ActiveAdmin.

Once you’re done, your `client/index.js` should look something like this:

```jsx
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
```

You can now fire it up:

```
yarn --cwd client start
```

It’ll automatically visit [http://localhost:3000/](http://localhost:3000/), and you’ll have a simple Create React App running. That is good. Also, if you haven't seen `yarn --cwd client` before, that tells yarn to run the command in the `client` directory. It also saves us `cd`-ing into and out of directories. Winning!

![The initial view when you generate a new Create React App](https://heroku-blog-files.s3.amazonaws.com/posts/1526300555-cra.jpg)

As I mentioned earlier, one of the best bits about working with Create React App and an API is that you can automatically proxy the API calls via the right port, without needing to swap anything between development and production. To do this, jump into your `client/package.json` and add a proxy property, like so:

```json
"proxy": "http://localhost:3001"
```

Your `client/package.json` file will look like this:

```json
{
  "name": "client",
  "version": "0.1.0",
  "private": true,
  "proxy": "http://localhost:3001",
  "dependencies": {
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "react-scripts": "1.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  }
}
```

(You might wonder why we’re proxying port `3001`. Once we hook everything up our scripts will be running the API on port `3001`, which is why we’ve been running Rails that way. Nice one picking up on that, though, eagle-eyes. Asking the right questions!)

`fetch` (along with a bunch of fancy new language features and polyfills [you should 100% check out](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#supported-language-features-and-polyfills)) is included with Create React App, so our front end is ready to make calls to the API. But right now that would be pretty pointless—we’ll need some data to actually fetch. So let’s get this smoothie party started.

We’ll need two relations, the `Drinks`, and the `Ingredients` that those drinks are made with. You’ll also need a blender, but honestly, if you don’t have one handy an apple juice with a couple of ice cubes is still so delicious. Promise.

Now normally I’d say avoid scaffolding in Rails, because you end up with a ton of boilerplate code that you have to delete. For the purposes of the exercise, we’re going to use it, and then end up with a ton of boilerplate code that we have to delete. Do what I say, not what I do.

_Before_ that though, I should mention something. One downside to ActiveAdmin using `inherited_resources`, which reduces the boilerplate for Rails controllers, is that Rails then uses it when you scaffold _anything_ in your app. That breaks stuff:

```
$ bin/rails g scaffold Drink title:string description:string steps:string source:string
Running via Spring preloader in process 38277
Expected string default value for '--serializer'; got true (boolean)
      invoke active_record
      create db/migrate/20170302183027_create_drinks.rb
      create app/models/drink.rb
      invoke test_unit
      create test/models/drink_test.rb
      create test/fixtures/drinks.yml
      invoke resource_route
       route resources :drinks
      invoke serializer
      create app/serializers/drink_serializer.rb
      invoke inherited_resources_controller
Could not find "api_controller.rb" in any of your source paths. Your current source paths are:
/usr/local/var/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/bundler/gems/inherited_resources-615b0d5c37a4/lib/generators/rails/templates
```
<figcaption>"Could not find" is never a good start to the last line of output.</figcaption>

Fortunately, this is a solvable problem. You just need to tell Rails to use the regular scaffolding process. You know, from the good old days.

Just remind Rails which `scaffold_controller` to use in your `config/application.rb` and we can be on our way:

```ruby
config.app_generators.scaffold_controller = :scaffold_controller
```

Your `config/application.rb` should look something like this, and everything should be right with the world again:

```ruby
require_relative 'boot'

require "rails"
# Pick the frameworks you want:
require "active_model/railtie"
require "active_job/railtie"
require "active_record/railtie"
require "active_storage/engine"
require "action_controller/railtie"
require "action_mailer/railtie"
require "action_view/railtie"
require "action_cable/engine"
require "sprockets/railtie"
require "rails/test_unit/railtie"

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module ListOfIngredients
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.

    # Only loads a smaller set of middleware suitable for API only apps.
    # Middleware like session, flash, cookies can be added back manually.
    # Skip views, helpers and assets when generating a new resource.
    config.api_only = true
    config.app_generators.scaffold_controller = :scaffold_controller

    # Middleware for ActiveAdmin
    config.middleware.use Rack::MethodOverride
    config.middleware.use ActionDispatch::Flash
    config.middleware.use ActionDispatch::Cookies
    config.middleware.use ActionDispatch::Session::CookieStore
  end
end
```

This seems like a good moment for a shout out to the hours I spent trying to understand this particular error by typing every variation of it into StackOverflow.

Back to scaffolding—let's start with the `Drink` model:

```
bin/rails g scaffold Drink title:string description:string steps:string source:string
```

Then, the `Ingredient` model:

```
bin/rails g scaffold Ingredient drink:references description:string
```

Notice that the `Ingredient` references the `Drink`. This tells the `Ingredient` model to `belong_to` the `Drink`, which is part of the whole `has_many` relative database association thing.

See, my Relational Databases 101 comp-sci class was totally worth it.

Unfortunately this won’t tell your `Drink` model to `has_many` of the `Ingredient` model, so you’ll also need to add that to `app/models/drink.rb` all by yourself:

```ruby
class Drink < ApplicationRecord
  has_many :ingredients
end
```

Then we can migrate and tell ActiveAdmin about our new friends:

```
bin/rake db:migrate
bin/rails generate active_admin:resource Drink
bin/rails generate active_admin:resource Ingredient
```

Go team!

Now, Rails is a security conscious beast, so you’ll need to add some stuff to the two files ActiveAdmin will have generated, `app/admin/drink.rb` and `app/admin/ingredient.rb`. Specifically, you’ll need to permit ActiveAdmin to edit the content in your database, which, when you think about it, is pretty reasonable.

First up, `app/admin/drink.rb`:

```ruby
ActiveAdmin.register Drink do
  permit_params :title, :description, :steps, :source

# See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
#
# permit_params :list, :of, :attributes, :on, :model
#
# or
#
# permit_params do
# permitted = [:permitted, :attributes]
# permitted << :other if params[:action] == 'create' && current_user.admin?
# permitted
# end

end
```

Then `app/admin/ingredient.rb`:

```ruby
ActiveAdmin.register Ingredient do
  permit_params :description, :drink_id

# See permitted parameters documentation:
# https://github.com/activeadmin/activeadmin/blob/master/docs/2-resource-customization.md#setting-up-strong-parameters
#
# permit_params :list, :of, :attributes, :on, :model
#
# or
#
# permit_params do
# permitted = [:permitted, :attributes]
# permitted << :other if params[:action] == 'create' && current_user.admin?
# permitted
# end

end
```

Without `permit_params`, you can never edit your delicious drink recipes. Not on my watch.

In our routes, we’ll need to hook up the drinks resource. I like to scope my API calls to `/api`, so let’s do that.

```ruby
scope '/api' do
  resources :drinks
end
```

You can also remove these two declarations:

```ruby
resources :ingredients
resources :drinks
```

Your file should look something like this:

```ruby
Rails.application.routes.draw do
  devise_for :admin_users, ActiveAdmin::Devise.config
  ActiveAdmin.routes(self)

  scope '/api' do
    resources :drinks
  end
end
```

Next up, start the server:

```
bin/rails s -p 3001
```

And you should be able to visit `http://localhost:3001/api/drinks` to see… _drumroll..._

```
[]
```

Nothing.

So, we should probably add some drinks. We can do that by populating `db/seeds.rb`, which is a file that allows you to add data to your database. You may notice a line is already here:

```ruby
AdminUser.create!(email: 'admin@example.com', password: 'password', password_confirmation: 'password') if Rails.env.development?
```

To ensure we can log onto our CMS in production, let’s remove the `if Rails.env.development?` conditional that ActiveAdmin has added:

```ruby
AdminUser.create!(email: 'admin@example.com', password: 'password', password_confirmation: 'password')
```
<figcaption>Just a friendly reminder to be a good internet citizen and update your password in production as soon as you seed it.</figcaption>

To save time, and so you don’t have to source your own recipes, I prepared two tasty smoothies and one terrible pun.

Add the recipes below:

```ruby
breakfast_smoothie = Drink.create(
  title: "Two-Minute Breakfast Boost",
  description: "Whizz up a low-fat breakfast smoothie in no time. Use banana with other soft fruit, plus honey for a little sweetness and oats for slow-release fuel.",
  steps: "Put all the ingredients in a blender and whizz for 1 min until smooth. Pour the mixture into two glasses to serve.",
  source: "https://www.bbcgoodfood.com/recipes/two-minute-breakfast-smoothie"
)
breakfast_smoothie.ingredients.create(description: "1 banana")
breakfast_smoothie.ingredients.create(description: "1 tbsp porridge oats")
breakfast_smoothie.ingredients.create(description: "80g soft fruit (like mango or strawberries)")
breakfast_smoothie.ingredients.create(description: "150ml milk")
breakfast_smoothie.ingredients.create(description: "1 tsp honey")
breakfast_smoothie.ingredients.create(description: "1 tsp vanilla extract")

kale_smoothie = Drink.create(
  title: "Kale And Hearty Smoothie",
  description: "Give yourself a dose of vitamin C in the morning with this vegan green smoothie. Along with kale and avocado, there's a hit of zesty lime and pineapple.",
  steps: "Put all of the ingredients into a bullet or smoothie maker, add a large splash of water and blitz. Add more water until you have the desired consistency.",
  source: "https://www.bbcgoodfood.com/recipes/kale-smoothie",
)
kale_smoothie.ingredients.create(description: "2 handfuls kale")
kale_smoothie.ingredients.create(description: "½ avocado")
kale_smoothie.ingredients.create(description: "½ lime, juice only")
kale_smoothie.ingredients.create(description: "large handful frozen pineapple chunks")
kale_smoothie.ingredients.create(description: "medium-sized chunk ginger")
kale_smoothie.ingredients.create(description: "1 tbsp cashew nuts")
kale_smoothie.ingredients.create(description: "1 banana, optional")
```

Your `db/seeds.rb` file should now look something like this:

```ruby
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
#
# Examples:
#
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
# Character.create(name: 'Luke', movie: movies.first)
AdminUser.create!(email: 'admin@example.com', password: 'password', password_confirmation: 'password')

breakfast_smoothie = Drink.create(
  title: "Two-Minute Breakfast Boost",
  description: "Whizz up a low-fat breakfast smoothie in no time. Use banana with other soft fruit, plus honey for a little sweetness and oats for slow-release fuel.",
  steps: "Put all the ingredients in a blender and whizz for 1 min until smooth. Pour the mixture into two glasses to serve.",
  source: "https://www.bbcgoodfood.com/recipes/two-minute-breakfast-smoothie"
)
breakfast_smoothie.ingredients.create(description: "1 banana")
breakfast_smoothie.ingredients.create(description: "1 tbsp porridge oats")
breakfast_smoothie.ingredients.create(description: "80g soft fruit (like mango or strawberries")
breakfast_smoothie.ingredients.create(description: "150ml milk")
breakfast_smoothie.ingredients.create(description: "1 tsp honey")
breakfast_smoothie.ingredients.create(description: "1 tsp vanilla extract")

kale_smoothie = Drink.create(
  title: "Kale And Hearty Smoothie",
  description: "Give yourself a dose of vitamin C in the morning with this vegan green smoothie. Along with kale and avocado, there's a hit of zesty lime and pineapple.",
  steps: "Put all of the ingredients into a bullet or smoothie maker, add a large splash of water and blitz. Add more water until you have the desired consistency.",
  source: "https://www.bbcgoodfood.com/recipes/kale-smoothie",
)
kale_smoothie.ingredients.create(description: "2 handfuls kale")
kale_smoothie.ingredients.create(description: "½ avocado")
kale_smoothie.ingredients.create(description: "½ lime, juice only")
kale_smoothie.ingredients.create(description: "large handful frozen pineapple chunks")
kale_smoothie.ingredients.create(description: "medium-sized chunk ginger")
kale_smoothie.ingredients.create(description: "1 tbsp cashew nuts")
kale_smoothie.ingredients.create(description: "1 banana, optional")
```

Now it’s just a case of seeding the database with `bin/rake db:reset`.

```
bin/rake db:reset
```

It’s worth noting that this will recreate your database locally—including resetting your admin password back to `password`. If your server is running you’ll need to restart it, too:

Now when you refresh you should see:

![Our recipes in RSS format](https://heroku-blog-files.s3.amazonaws.com/posts/1526300601-rss-initial.jpg)<figcaption>Text smoothies!</figcaption>

So, we’re pretty good to go on the database front. Let’s just massage our scaffolded controllers a little. First, let’s cut back the `DrinksController`. We can make sure `def index` only returns the `id` and `title` of each drink, and we can make sure `def show` includes the `id` and `description` of each ingredient of the drink. Given how little data is being sent back, you could just grab everything from `index`, but for the purposes of showing how this could work in the Real World, let’s do it this way.

You’ll want to make sure your controllers are inheriting from `ApiController`, too. Jump into your `drinks_controller.rb` and replace it with the following:

```ruby
class DrinksController < ApiController
  # GET /drinks
  def index
    @drinks = Drink.select("id, title").all
    render json: @drinks.to_json
  end

  # GET /drinks/:id
  def show
    @drink = Drink.find(params[:id])
    render json: @drink.to_json(:include => { :ingredients => { :only => [:id, :description] }})
  end
end
```

And let’s just get rid of 99% of `ingredients_controller.rb`, because it’s not going to be doing a lot:

```ruby
class IngredientsController < ApiController
end
```
<figcaption>So minimal!</figcaption>

And now we have some fancy data to feed the client. Good for us! This is a big chunk of the setup, and you’re doing great. Maybe celebrate by taking a break? You have earned it.

When you’re back, let’s create a `Procfile` in the root of the app for running the whole setup. If you haven’t used them before, [you can read about them here](https://devcenter.heroku.com/articles/procfile).

We’ll call it `Procfile.dev`, because while we _do_ need to run a Node server locally, we’ll be deploying a pre-built bundle to Heroku, and we won’t need to run a Node server there. Having a Node server and Rails server locally massively speeds up development time, and it is pretty great, but it’s overkill for production. Your `Procfile.dev` should look like this:

```
web: PORT=3000 yarn --cwd client start
api: PORT=3001 bundle exec rails s
```

Procfiles are managed by the `heroku` CLI, which, if you don’t have installed, [you can get right here](https://devcenter.heroku.com/articles/heroku-cli).

Once that’s sorted, just run:

```
heroku local -f Procfile.dev
```

But hey, who wants to type that every single time? Why not make a rake task to manage doing it for you? Just add `start.rake` to your `/lib/tasks` folder:

```ruby
namespace :start do
  task :development do
    exec 'heroku local -f Procfile.dev'
  end
end

desc 'Start development server'
task :start => 'start:development'
```

And from there all you need to do to fire up your development environment is run:

```
bin/rake start
```
<figcaption>One command to fire up two servers? Magic!</figcaption>

That step was a lot. Let’s break down what’s happening here.

`heroku` will start the front end, `/client`, on port `3000`, and the API on port `3001.` It’ll then open the client, `http://localhost:3000` in your browser. You can access ActiveAdmin via the API, at `http://localhost:3001/admin`, just like you’ve been doing all along.

Which means we can now sort out the React app.

The simplest thing is to just check it works. Edit your `client/app.js`:

```jsx
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  componentDidMount() {
    window.fetch('/api/drinks')
      .then(response => response.json())
      .then(json => console.log(json))
      .catch(error => console.log(error));
  }
  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h2>Welcome to React</h2>
        </div>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;
```

In your browser console, you should see the API call logged.

```
[{id: 1, title: "Two-Minute Breakfast Boost"}, {id: 2, title: "Kale And Hearty Smoothie"}]
```

We can 100% use those id’s to grab the actual details of each smoothie in Rails. Sure, we could’ve just sent everything from the server because it’s only two drinks, but I figure this is closer to how you’d really build something.

Now, if you'd rather skip setting up the front end application, you can grab the `client` folder [from the repo](http://github.com/superhighfives/list-of-ingredients). Otherwise, install the following dependencies:

```
yarn --cwd client add semantic-ui-react semantic-ui-css
```

And add them to your `/client` app. First, add the css to `client/src/index.js`:

```jsx
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import 'semantic-ui-css/semantic.css'
import './index.css'

ReactDOM.render(<App />, document.getElementById('root'))
```

And add all the fancy bells and whistles to your `client/src/app.js`:

```jsx
import React, { Component } from 'react'
import { Container, Header, Segment, Button, Icon, Dimmer, Loader, Divider } from 'semantic-ui-react'

class App extends Component {
  constructor () {
    super()
    this.state = {}
    this.getDrinks = this.getDrinks.bind(this)
    this.getDrink = this.getDrink.bind(this)
  }

  componentDidMount () {
    this.getDrinks()
  }

  fetch (endpoint) {
    return window.fetch(endpoint)
      .then(response => response.json())
      .catch(error => console.log(error))
  }

  getDrinks () {
    this.fetch('/api/drinks')
      .then(drinks => {
        if (drinks.length) {
          this.setState({drinks: drinks})
          this.getDrink(drinks[0].id)
        } else {
          this.setState({drinks: []})
        }
      })
  }

  getDrink (id) {
    this.fetch(`/api/drinks/${id}`)
      .then(drink => this.setState({drink: drink}))
  }

  render () {
    let {drinks, drink} = this.state
    return drinks
      ? <Container text>
        <Header as='h2' icon textAlign='center' color='teal'>
          <Icon name='unordered list' circular />
          <Header.Content>
            List of Ingredients
          </Header.Content>
        </Header>
        <Divider hidden section />
        {drinks && drinks.length
          ? <Button.Group color='teal' fluid widths={drinks.length}>
            {Object.keys(drinks).map((key) => {
              return <Button active={drink && drink.id === drinks[key].id} fluid key={key} onClick={() => this.getDrink(drinks[key].id)}>
                {drinks[key].title}
              </Button>
            })}
          </Button.Group>
          : <Container textAlign='center'>No drinks found.</Container>
        }
        <Divider section />
        {drink &&
          <Container>
            <Header as='h2'>{drink.title}</Header>
            {drink.description && <p>{drink.description}</p>}
            {drink.ingredients &&
              <Segment.Group>
                {drink.ingredients.map((ingredient, i) => <Segment key={i}>{ingredient.description}</Segment>)}
              </Segment.Group>
            }
            {drink.steps && <p>{drink.steps}</p>}
            {drink.source && <Button basic size='tiny' color='teal' href={drink.source}>Source</Button>}
          </Container>
        }
      </Container>
      : <Container text>
        <Dimmer active inverted>
          <Loader content='Loading' />
        </Dimmer>
      </Container>
  }
}

export default App
```

I should clarify that this is what I like to call “proof of concept code”, rather than “well refactored code”. But, given we're already having a look at it, the main bit worth reviewing is `getDrink`:

```javascript
getDrink (id) {
  this.fetch(`/api/drinks/${id}`)
    .then(drink => this.setState({drink: drink}))
}
```

This allows us to grab a specific drink based on its id. You can test it in the browser by visiting [http://localhost:3001/api/drinks/1:](http://localhost:3001/api/drinks/1:)

![A single recipe in RSS format, with ingredients](https://heroku-blog-files.s3.amazonaws.com/posts/1526300633-rss-all.jpg)

While we’re here, you can also add some simple styles to your `client/src/index.css`:

```css
body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
}

#root {
  padding: 4rem 0;
}
```

You now should have a fancy front end that uses [Semantic UI](http://react.semantic-ui.com/) and looks something like this:

![Your Create React App pulling from the Rails API](https://heroku-blog-files.s3.amazonaws.com/posts/1526300660-site-initial.jpg)<figcaption>Kale and Hearty! Get it?</figcaption>

* * *

## Step 4: Get everything ready for production

### With Rails serving the Webpack bundle

* * *

So, how do we get our Rails app serving the Webpack bundle in production?

That’s where the magic of Heroku's `heroku-postbuild` comes in. Heroku will build the app, then copy the files into the `/public` directory to be served by Rails. We end up running a single Rails server managing our front end and our back end. It’s win-win! There are a couple of steps to make that happen.

First up, let’s make a `package.json` file in the root of the app, which tells Heroku how to compile the Create React App. The `heroku-postbuild` command will get run after Heroku has built [your application, or slug](https://devcenter.heroku.com/articles/how-heroku-works).

You may also notice that the `build` command uses `yarn --cwd client`, which tells yarn to run those commands in the `client` directory.

```json
{
  "name": "list-of-ingredients",
  "license": "MIT",
  "engines": {
    "node": "8.9.4",
    "yarn": "1.6.0"
  },
  "scripts": {
    "build": "yarn --cwd client install && yarn --cwd client build",
    "deploy": "cp -a client/build/. public/",
    "heroku-postbuild": "yarn build && yarn deploy"
  }
}
```

On the plus side, this step is super short, which is just as well because my hands are getting sore.

* * *

## Step 5: Deploy it to Heroku

### And celebrate, because you’ve earned it

* * *

The finish line approaches! Soon, everything the light touches will be yours, including a fresh, healthy beverage.

Let’s make a `Procfile`, in the root, for production. It will tell Heroku how to run the Rails app. Add the following:

```
web: bundle exec rails s
release: bin/rake db:migrate
```

Note the `release` command—this is run by Heroku just before a new release of the app is deployed, and we’ll use it to make sure our database is migrated. You can read more about [release phase](https://devcenter.heroku.com/articles/release-phase) here.

We'll also need a `secrets.yml` file, which lives in `config`. This is required by Devise, which handles the authentication for ActiveAdmin. You'll need to make a `config/secrets.yml` file, and it should look like this:

```yaml
development:
  secret_key_base: 

test:
  secret_key_base: 

production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
```

We'll need to add two keys, one for development and one for test. Fortunately, Rails is here to help. Just run:

```
bin/rake secret | pbcopy
```

This will generate a secret key, and add it to your clipboard. Just paste it after `secret_key_base` below `development`. Repeat the same for `test`, and you should end up with a `config/secrets.yml` that looks something like this:

```yaml
development:
  secret_key_base: A_LONG_STRING_OF_LETTERS_AND_NUMBERS

test:
  secret_key_base: A_DIFFERENT_LONG_STRING_OF_LETTERS_AND_NUMBERS

production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
```

And then let’s create a new Heroku app to get this thing over the finish line:

```
heroku apps:create
```

If you commit and push to Heroku right now, this looks to Heroku like a dual Rails / Node app, which is great. The thing is, your Node code needs to be executed first so it can be served by Rails. This is where [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks) come in — they transform your deployed code to run on Heroku. We can tell Heroku, via the terminal, to use two buildpacks (or build processes) in a specific order. First `nodejs`, to manage the front end build, and then `ruby`, to run Rails:

```
heroku buildpacks:add heroku/nodejs --index 1
heroku buildpacks:add heroku/ruby --index 2
```

With that sorted, we can deploy and build our beverage-based app:

```
git add .
git commit -vam "Initial commit"
git push heroku master
```

Heroku will follow the order of the buildpacks, building `client`, and then firing up Rails.

One last thing—you’ll need to seed your database on Heroku, or ActiveAdmin will not be thrilled (and you won’t be able to log in). We won't need to worry about migrating, because that'll happen behind the scenes through the release script in our `Procfile`. Let’s seed so we can login and change the `/admin` password:

```
heroku run rake db:seed
```

And finally:

```
heroku open
```

And there you have it:

![The final site, hosted on Heroku](https://heroku-blog-files.s3.amazonaws.com/posts/1526300703-site-final.jpg)<figcaption>Check it out at <a href="https://list-of-ingredients.herokuapp.com/">https://list-of-ingredients.herokuapp.com/</a></figcaption>

When you visit your app you’ll see your Create React App on the client side, displaying some delicious smoothie recipes. You’ll also be able hit `/admin` (for example, [https://list-of-ingredients.herokuapp.com/admin](https://list-of-ingredients.herokuapp.com/admin)) and access your database using that truly terrible username and password ActiveAdmin chose for you. Again, I’d recommend changing those on production ASAP. I did, in case anyone was thinking of changing my demo recipes to be less delicious.

* * *

## Bonus round: Single page apps

### Handling routes with your single page app

* * *

Now, you may at this point want to add different pages, handled within your Create React App, using something like [React Router](https://github.com/ReactTraining/react-router). This will require a few additions to the Rails app as well. Let’s get started!

First up, we’re going to tell Rails to pass any HTML requests that it doesn’t catch to our Create React App.

In your `app/controllers/application_controller.rb`, add a `fallback_index_html` method:

```ruby
def fallback_index_html
  render :file => 'public/index.html'
end
```

It should look something like this:

```ruby
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  def fallback_index_html
    render :file => 'public/index.html'
  end
end
```

And at the bottom of your `config/routes.rb`:

```ruby
get '*path', to: "application#fallback_index_html", constraints: ->(request) do
  !request.xhr? && request.format.html?
end
```

So it looks something like this:

```ruby
Rails.application.routes.draw do
  devise_for :admin_users, ActiveAdmin::Devise.config
  ActiveAdmin.routes(self)

  scope '/api' do
    resources :drinks
  end

  get '*path', to: "application#fallback_index_html", constraints: ->(request) do
    !request.xhr? && request.format.html?
  end
end
```

That way Rails will pass anything it doesn’t match over to your `client/index.html` so that React Router can take over. Winning!

From here, we can implement React Router and catch some 404’s. First off, let’s install React Router:

```
yarn --cwd client add react-router-dom
```

We’ll need to move our `client/src/App.js` into its own component, so we can use the `App` class to handle routes and navigation. Rename `App.js` to `Home.js`, and update the class name to `Home`. Your `client/src/Home.js` should look like this:

```jsx
import React, { Component } from 'react'
import { Container, Header, Segment, Button, Icon, Dimmer, Loader, Divider } from 'semantic-ui-react'

class Home extends Component {
  constructor () {
    super()
    this.state = {}
    this.getDrinks = this.getDrinks.bind(this)
    this.getDrink = this.getDrink.bind(this)
  }

  componentDidMount () {
    this.getDrinks()
  }

  fetch (endpoint) {
    return window.fetch(endpoint)
      .then(response => response.json())
      .catch(error => console.log(error))
  }

  getDrinks () {
    this.fetch('/api/drinks')
      .then(drinks => {
        if (drinks.length) {
          this.setState({drinks: drinks})
          this.getDrink(drinks[0].id)
        } else {
          this.setState({drinks: []})
        }
      })
  }

  getDrink (id) {
    this.fetch(`/api/drinks/${id}`)
      .then(drink => this.setState({drink: drink}))
  }

  render () {
    let {drinks, drink} = this.state
    return drinks
      ? <Container text>
        <Header as='h2' icon textAlign='center' color='teal'>
          <Icon name='unordered list' circular />
          <Header.Content>
            List of Ingredients
          </Header.Content>
        </Header>
        <Divider hidden section />
        {drinks && drinks.length
          ? <Button.Group color='teal' fluid widths={drinks.length}>
            {Object.keys(drinks).map((key) => {
              return <Button active={drink && drink.id === drinks[key].id} fluid key={key} onClick={() => this.getDrink(drinks[key].id)}>
                {drinks[key].title}
              </Button>
            })}
          </Button.Group>
          : <Container textAlign='center'>No drinks found.</Container>
        }
        <Divider section />
        {drink &&
          <Container>
            <Header as='h2'>{drink.title}</Header>
            {drink.description && <p>{drink.description}</p>}
            {drink.ingredients &&
              <Segment.Group>
                {drink.ingredients.map((ingredient, i) => <Segment key={i}>{ingredient.description}</Segment>)}
              </Segment.Group>
            }
            {drink.steps && <p>{drink.steps}</p>}
            {drink.source && <Button basic size='tiny' color='teal' href={drink.source}>Source</Button>}
          </Container>
        }
      </Container>
      : <Container text>
        <Dimmer active inverted>
          <Loader content='Loading' />
        </Dimmer>
      </Container>
  }
}

export default Home
```

And let’s make a component to display our 404, `client/src/NotFound.js`.

```jsx
import React, { Component } from 'react'
import { Container, Button } from 'semantic-ui-react'
import { Link } from 'react-router-dom'

class NotFound extends Component {
  render () {
    return <Container text textAlign='center'>
      <h1>404: Not found</h1>
      <Button as={Link} to='/'>Back to home</Button>
    </Container>
  }
}

export default NotFound
```

Make a new `client/src/App.js`, and add in some routing:

```jsx
import React, { Component } from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Home from './Home'
import NotFound from './NotFound'

class App extends Component {
  render () {
    return <Router>
      <Switch>
        <Route path='/' exact component={Home} />
        <Route component={NotFound} />
      </Switch>
    </Router>
  }
}

export default App
```

Now you can run jump back into your root directly, run `bin/rake start`, and visit any URL that isn’t the root to get your 404.

![An example of the 404 page](https://heroku-blog-files.s3.amazonaws.com/posts/1526306039-404.jpg)<figcaption>The very worst 404—no puppies</figcaption>

From there, you can add as many routes as you like, and if Rails doesn’t catch them first, they’ll be served by your client. Nice work!

To test this on your live app commit your changes and push:

```
git add .
git commit -vam "Added react router"
git push heroku master
heroku open
```

And visit any random page, like `/puppies`. You should see your 404, served by Create React App. Nice work!

* * *

This isn’t exactly the most thrilling demo (tasty as it may be) but hopefully it gets you up and running. All the ingredients to make a delicious Rails API / ActiveAdmin / Create React App flavoured beverage are here, and the sky’s the limit.

Again, you can see a ready-to-go repo here, too, including a Heroku button for instant deployment:[http://github.com/heroku/list-of-ingredients](http://github.com/superhighfives/list-of-ingredients)

Thanks for taking the time to have a look, and I genuinely hope you celebrated with a smoothie.

Shout out to [Roman Rott](https://rrott.com/blog/ror/rails-5-api-with-activeadmin-integration.html), [Carlos Ramirez III](http://www.carlosramireziii.com), and [Full Stack React](https://www.fullstackreact.com/articles/how-to-get-create-react-app-to-work-with-your-rails-api/) for the inspiration to put this together. And a massive thank you to [Glen](http://twitter.com/glenmaddern) and [Xander](https://twitter.com/quitexander) for taking the time to make suggestions and proofread the first take, and to [Chris](https://twitter.com/crc) for working with me on this one.

If you have any questions or comments [say hi via Twitter](https://twitter.com/superhighfives). Alternatively, enjoy a GIF of a dog saying "well done"!

![](https://media.giphy.com/media/H3ElMwEfyhJU4/giphy.gif)