Using redux-api-middleware with Rails
At work, we’ve been building front-end applications with React and Redux. Our back ends are generally written in Rails.
In order to make the API calls to the back end, we’ve been using the redux-api-middleware library which works quite well for us. We’ve added a bit of infrastructure that makes communicating with a Rails server a bit easier.
Another library we use quite a bit is Ramda. I talked about that in Using Ramda With Redux.
As with some of my earlier articles, this code is part of Zeal’s react-boilerplate.
redux-api-middleware
redux-api-middleware is a Redux middleware that handles specially-formatted Redux actions.  It exposes a special property name (CALL_API) and any action that contains that property is handled by the middleware.  The value of CALL_API is a descriptor that specifies how an API call should be made and what Redux actions to dispatch at various stages of the process.  The possible actions are a REQUEST action that is dispatched just before making the API call, a SUCCESS action that is dispatched when the call succeeds, and a FAILURE action that is dispatched when the call fails.
Here’s a minimal example of a redux-api-middleware action, taken from the README:
import { CALL_API } from `redux-api-middleware`;
 {
  [CALL_API]: {
    endpoint: 'http://www.example.com/api/users',
    method: 'GET',
    types: ['REQUEST', 'SUCCESS', 'FAILURE']
  }
}
Problems to Solve
As we began working with Redux and Rails, we started running into a fair bit of duplicated code and wanted a way to extract it into one place. Duplicated pieces included:
- Converting from JavaScript-style camelCase keys to Rails-style snake_case keys.
 - Converting payloads to “stringified” JSON.
 - Adding common request headers
 
Solution
The version of this code in the react-boilerplate repository is somewhat more involved than this, as it solves a couple of other problems and also injects some dependencies to make testing easier. I’m leaving those out to make the code easier to understand.
To eliminate duplication, we introduced a utility function, callApi, that we use when making calls to our Rails application’s API:
export function callApi(callDescriptor) {
  return {
    [CALL_API]: tranformCallDescriptor(callDescriptor)
  }
}
callApi takes a redux-api-middleware call descriptor, transforms it, and returns a Redux action that can be dispatched through the middleware.  I’ll talk about transformCallDescriptor in a moment.
We can already see that none of the callers of callApi need to worry about the CALL_API property name; that’s now encapsulated in this function.  The callers still need to know the format of the call descriptor, but they are now slightly less coupled to redux-api-middleware.
A simple use of callApi in a Redux action creator might look like this:
export function fetchUser(id) {
  return callApi({
    endpoint: `/api/v1/users/${id}`,
    types: [
      ActionTypes.FETCH_USER_REQUEST,
      ActionTypes.FETCH_USER_SUCCESS,
      ActionTypes.FETCH_USER_FAILURE,
    ]
  })
}
transformCallDescriptor looks like this:
function tranformCallDescriptor(callDescriptor) {
  const transform = compose(
    evolve({
      body: compose(JSON.stringify, decamelizeKeys),
      types: compose(
        adjust(tranformSuccessPayload, 1),
        adjust(transformFailurePayload, 2)
      )
    }),
    merge({
      method: 'GET'
    }),
    mergeWith(merge, {
      headers: {
        'Content-Type': 'application/json'
      }
    })
  )
   return transform(callDescriptor)
}
As I write this, I see that I could extract some named helper functions that would make this a bit clearer. What is this doing?
First, we use some Ramda functional magic to define a local transform function and then we apply that to the call descriptor.
- 
    
We use
composeto build up a function that applies multiple transformations. Sayingcompose(f, g, h)(argument)is the same as sayingf(g(h(argument))). We callhwith the argument, pass the result of that tog, the result of that tof, and return the result of that as the final result. - 
    
Reading from the bottom up, we supply a default header that sets the
Content-Typetoapplication/json. We could just usemergehere, but then any headers provided by the caller would overwrite our header. Instead, we want to merge caller-supplied headers with our header, so we usemergeWithandmerge. The caller can provide its ownContent-Typeheader and that will be used, but if it doesn’t, we’ll use our default value. - 
    
Next, we default the HTTP method to
GET. Again, the caller can provide a different method, but if none is given, we default toGET. - 
    
Finally, we use Ramda’s
evolvefunction to modify some of the properties passed in by the caller.evolvetakes an object that specifies a transformation function for each key. Here we’re saying we want to transform thebodyand thetypesproperties. - 
    
We transform the
bodyby converting all of the keys from camelCase to snake_case using the humps library’sdecamelizeKeysfunction. We then take the result and callJSON.stringifyto convert the body to a JSON string. - 
    
We then transform several of the elements of the
typesarray using Ramda’sadjustfunction.typesis the array of three action type descriptors that I mentioned above. The first specifies theREQUESTaction that will be dispatched by redux-api-middleware; the second specifies theSUCCESSaction; and the third theFAILUREaction. See the Lifecycle documentation of redux-api-middleware for more details. 
Let’s look at how we transform the SUCCESS action first:
function tranformSuccessPayload(type) {
  return {
    ...objectize(type),
    payload: (action, state, res) =>
      getJSON(res).then(camelizeKeys)
  }
}
 function objectize(type) {
  return contains(typeof type, ['string', 'symbol'])
    ? { type }
    : type
}
As described in Customizing the dispatched FSAs documentation, redux-api-middleware allows the SUCCESS type descriptor to be a string or symbol OR an object that serves as a blueprint for creating the Redux action.
We’re not sure which version our callers will supply, so we have a little utility function called objectize that ensures we have an object.  contains is another Ramda function.
We then merge in a new payload property using ES7 object-spread notation.  We could have used Ramda’s merge here as well but for simple things like this, I find object-spread more readable.
The new payload property is a function that does exactly what redux-api-middleware would do (using its getJSON utility function), but then uses humps’ camelizeKeys to transform the keys in the response from snake_case to camelCase.  This is the reverse transformation of the one we applied to the body above.
transformFailurePayload is almost identical, but we need to wrap the transformed response in a redux-api-middleware ApiError to match the default behavior:
function transformFailurePayload(type) {
  return {
    ...objectize(type),
    payload: (action, state, res) => getJson(res).then(json =>
      new ApiError(res.status, res.statusText, camelizeKeys(json))
    )
  )
}
With callApi, we can now simplify the code we use to make API calls and remove a bunch of copy-pasted logic.
We’ve added other transformations on some of our client projects.  For example, in one case we also transform the endpoint property to prepend a hostname and common URL prefix.  In another case, we look up the API authorization token and merge in an Authorization header along with the Content-Type header described above.
This little set of utility functions allows for a lot of common boilerplate to be extracted to one place instead of being scattered all over the code.