Written by Pete Corey on Apr 3, 2017.

Contrary to what I said a couple weeks ago, I’ve decided to build the front-end of Inject Detect, as a React application.

Wanting to get off the ground quickly, I wanted to use Create React App to generate my React application boilerplate and tooling. While I wanted to use Create React App, I still wanted my React app to live within the context of a Phoenix application.

How could I combine these two worlds?

It took a little fiddling, but I’ve landed on a configuration that is working amazingly well for my needs. Read on to find out how I integrate all of the magic of Create React App with the power of a Phoenix application!

Front-end Demolition

To start, we won’t need Brunch to manage our project’s front-end static assets.

When creating a new Phoenix project, either leave out Brunch (mix phoenix.new ... --no-brunch), or remove brunch from your existing project by removing the Brunch watcher from your config/dev.exs configuration, and by removing brunch-config.js, package.json, and the contents of node_modules and web/static.

We also won’t need any of the initial HTML templates a new Phoenix project generates for us.

You can skip the generation of these initial files by giving mix phoenix.new a --no-html flag, or you can just ignore them for now.

Now that we’ve torn a massive hole in the front-end of our Phoenix application, let’s fill it back in with a React application!

Creating a React App

We’ll be using Create React App to generate our React application’s boilerplate and to wire up our front-end tooling.

We want Create React App to work largely without any interference or tweaking, so we’ll create our new front-end application within the /priv folder of our Phoenix project:

cd priv
create-react-app hello_create_react_app

What you choose to call your React project is entirely up to you. In this case, I chose to replicate the name of the overarching Phoenix project.

To give myself a little extra control over the base index.html file generated by Create React App, I chose to immediately eject my React application at this point:

cd hello_create_react_app
npm run eject

You now have a fully functional front-end application. You can spin up your development server by running npm start within the priv/hello_create_react_app folder.

Your React application should automatically start serving from port 3000!

Front-end Meets Back-end

Now we’re in a strange spot.

In development our React application will spin up and run on port 3000 but has no out-of-the-box way to interact with our back-end. Similarly, our Phoenix application runs on port 4000, but no longer serves any kind of front-end.

How do we join the two?

The answer is fairly simple. Our React application should be built in such a way that it can be told to communicate with any back-end service over whatever medium we choose.

We configure this communication by passing in the URL of our backend service through environment variables. The Create React App tooling will populate env.process for us with any environment variables prefixed with REACT_APP_.

With this in mind, let’s connect our React application to our back-end using Apollo client:

const networkInterface = createNetworkInterface({
    uri: _.get(process.env, "REACT_APP_GRAPHQL_URL") || "http://localhost:4000/graphql"

Locally, we can set REACT_APP_GRAPHQL_URL when we spin up our Create React App tooling:

REACT_APP_GRAPHQL_URL="http://localhost:1337/graphql" npm start

During staging and production builds, we can set this environment variable to either our staging or production back-ends, respectively.

Lastly, we’ll need to configure our Phoenix server to allow requests from both of our development servers. Add the CORSPlug to your endpoint just before you plug in your router:

plug CORSPlug, origin: ["http://localhost:3000", "http://localhost:4000"]

For more flexibility, pull these CORS endpoints from an environmental configuration.

Deployment Freedom

Because our front-end application exists completely independently from our Phoenix application, we can deploy it anywhere.

Running npm run build in our priv/hello_create_react_app folder will build our application into a static bundle in the priv/hello_create_react_app/build folder.

This static bundle can be deployed anywhere you choose to deploy static assets, such as S3 or even GitHub Pages!

Because your front-end content is served elsewhere, your back-end Phoenix application will only be accessed to resolve queries or perform mutations, rather than to fetch every static asset requested by every user.

Out of the box, this inherent separation between the front-end and the back-end offers nice scaling opportunities for your application.

Serving React from Phoenix

While serving your front-end separately from your back-end can be a powerful tool, it’s often more of a burden when you’re just getting out of the gate.

Instead, it would be nice to be able to serve your front-end React application from your Phoenix application.

Thankfully, this is a breeze with a few configuration changes.

First things first, we want our application’s root URL to serve our react application, not a Phoenix template. Remove the get "/" hook from router.ex.

Now we’ll want to reconfigure our Phoenix endpoint to serve static assets from our React application’s build folder, not priv/static:

plug Plug.Static,
  at: "/", 
  from: "priv/hello_create_react_app/build/",
  only: ~w(index.html favicon.ico static)

Unfortunately, navigating to our root URL doesn’t load our application. However, navigating to /index.html does!

We’re almost there.

We need to tell Phoenix to load and serve static index.html files when they’re available. The plug_static_index_html plugin makes this a one line change.

Just before we wire up Plug.Static in our endpoint, configure the application’s root URL to serve index.html:

plug Plug.Static.IndexHtml,
  at: "/"

That’s it! We’re serving our React application statically from within our Phoenix application!

Now any deployments of our Phoenix application will contain and serve the last built React application in its entirety. Be sure to set your REACT_APP_ environment variables during your Phoenix release build process!

Final Thoughts

I’ve been developing with this setup for the past few weeks, and I’m extremely satisfied with it so far.

Using Create React App gets me off the ground quickly with a functioning boilerplate and fantastic tooling. I don’t have to waste time restructuring my Phoenix application or tweaking build configurations.

The ability to quickly integrate my React application with an Elixir-powered Phoenix back-end means that I don’t have to sacrifice speed of development for power or scalability.