Redux Fake Api middleware

Redux document describes an api middleware in Reducing Boilerplate. We do not need to write fetching code for every api now. There is also an api middleware module (redux-api-middleware) implemented in the same way in npm.

Api middleware helps a lot when deal with ajax calls. But I met a problem quickly. The needed APIs were not all implemented when I started building the components. What can I do with that?

Fake API

I did that almost in every project when I worked with jQuery in the old days. Instead of making http calls to the server directly in event handler,


$.ajax({
  url:'/echo/js/?js=hello%20world!',
    complete: function (response) {
      $('#output').html(response.responseText);
    },
    error: function () {
      $('#output').html('Bummer: there was an error!');
    },
  });

Ajax related codes were put in a file called api.js.


// api.js
// it's better to return a promise, but I ignore that here
function echo() {
  $.ajax(
   {...}    
  )
}

function getUsers() {
  return [{
    name: 'Jack',
    age: 13
  }]
}

var services = {
  echo: echo,
  getUsers: getUsers
}

I can just return fake data to emulate api if one service is not ready right now, just like what getUsers does in the sample code above.

But it's redux here, how can we do the same thing for it?

Fake API for Api Middleware

Remember the api middleware? I made another middleware called api fake and put it before api middleware.


import apifake from '../middleware/apifake'
applyMiddleware(thunk, apifake, api, reduxRouterMiddleware)

Note: apifake should go before api

When there is a [CALL_API] key in an action, apifake will check whether the related api is implemented.

  • pass to next middleware if the api is implemented already
  • return fake data if not

How does it work?


import { normalize } from 'normalizr'
import { camelizeKeys } from 'humps'
import { Schemas, CALL_API } from './api'
import * as fakedata from './fakedata'

const fakedEndpoint = {
  'v1/users': {
    'GET': fakedata.getUsers
  }
}

function callApi(token, endpoint, schema, method='GET', data={}) {
  const p = new Promise((resolve) => {
    // emulate network latency
    setTimeout(resolve, 1000)
  })

  return p.then(() => {
    let returned = fakedEndpoint[endpoint][method](data)
    const camelizedJson = camelizeKeys(returned)
    return Object.assign({}, schema !== Schemas.NO_SCHEMA ? normalize(camelizedJson, schema) : camelizedJson)
  })
}

// fake data for apis that are not implemented yet
export default store => next => action => {
  const callAPI = action[CALL_API]
  if (typeof callAPI === 'undefined') {
    return next(action)
  }

  let { endpoint } = callAPI

  const { schema, types, method, data } = callAPI

  // check whether the api has been implemented
  if (!fakedEndpoint[endpoint] || !fakedEndpoint[endpoint][method || 'GET']) {
    // if implemented, pass to api middleware
    return next(action)
  }

  function actionWith(data) {
    const finalAction = Object.assign({}, action, data)
    delete finalAction[CALL_API]
    return finalAction
  }

  const [ requestType, successType, failureType ] = types
  next(actionWith({ type: requestType }))

  return callApi(endpoint, schema, method, data).then(
    response => next(actionWith({
      response,
      type: successType
    })),
    error => next(actionWith({
      type: failureType,
      error: error.message || 'Something bad happened'
    }))
  )
}

Api fake middleware will delete [CALLAPI] key in the original action if the api has not been implemented yet. So an action with [CALLAPI] key will be handled by either api or apifake, but not both.

Luo Gang

Read more posts by this author.