Handling Dev- vs Production API Calls in create-react-app

May 10th, 2018

This post documents a brute-force approach I took to alternating between development and production databases in the PLURview app.

I built a simple apiAdapter.js file in my src/ directory, which sets an API_URL variable for my fetch() calls. PLURview is based on create-react-app, which has the ability to distinguish between dev and production through process.env variables.

The Problem

There were about 10 different calls to PLURview's internal API spread throughout the front-end codebase. The API URLs were hard-coded, which required a project-wide search-and-replace for every commit (local vs production).

The other problem was complexity of information when it came to researching how to accomplish this. The app is hosted on Heroku and it automatically updates from Github.

I looked into using Heroku's environment variables, which I had found useful in the past for storing API keys. All of my calls to external APIs go through the back-end, which is a private repo.

Ultimately a proper DevOps style solution appeared to be too heavy a solution for the amount of time I had to research and implement a solution.

The Solution

First, I modified my NPM scripts to set the dev environment variable:

    "scripts": {
        "start": "REACT_APP_STAGE=dev react-scripts start",
        ...
      }

When you set a variable starting with REACT_APP you'll have access to it in your app along the pattern of process.env.REACT_APP_STAGE.

Next, in apiAdapter.js I created a simple ternary based on the environment variable:

const API_URL = process.env.REACT_APP_STAGE === 'dev'
  ? 'http://localhost:3001/api/v1'
  : 'https://plurview-api.herokuapp.com/api/v1';

Finally, I collected all those fetch calls into apiAdapter.js so I could interpolate the API_URL based on the environment:

export const fetchArtists = () => {
  return fetch(`${API_URL}/artists`);
}

The adapter works the same way in the app as Redux actions. A component can import and use only the calls it needs:

import { fetchColorGuide } from '../../apiAdapter';

...

toggleDetails = () => {
    fetchColorGuide()
      .then(res => res.json());

Conclusion

This was a good intermediate step between ad hoc fetch() calls and something more elegant. In the process I found I probably had more API calls going on than necessary, and some of my naming conventions could use better semantics.

The process of abstracting these calls also shined some light on components and endpoints I was no longer using, which helped me pare down the codebase a bit overall.