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.