Skip to content

API Implementation

Gonçalo Marques edited this page Dec 22, 2017 · 1 revision

Structure

Concerning the API communication implementation, a 'data layer' was created. All the API implementation methods, adapter, mock data and constants are under /data of the project scaffolding.

The API implementation is done around a singleton, the API Adapter (can be found in /data/api/index.js). The instanceof this object has:

  • Client - The axios client that creates all the API connection using HTTP standards and returning Promises (can be found in data/api/api.client.js)
  • Endpoints - Where all the endpoints are defined with 2 parameters, url and method, to be used on the API Client (can be found in data/api/api.endpoints.js)
  • Services - A service is a set of functions to serve a certain domain of the application (documents, billing ...). Each function is responsible for one API connection (can be found in data/api/services)

In order to keep the consistency of keys across the application (every key on store is on camelCase), the API models file was created to map all the keys naming was created on data/api/api.models.js.

Models

There are 2 types of models in the API Models file:

  • DOMAIN_REQUEST_KEYS - Transforms the keys received from the API into the application's keys
  • DOMAIN_RESPONSE_KEYS - Transforms the keys from the application into the ones the API need

A more efficient way used on more complex objects, is to use a function to transpile the whole object and map the keys. Example:

export const parseCompaniesDetailsResponse = function (object) {
  return {
    'name': object.name,
    'address': object.address,
    'date': object.date,
    'taxClassification': object.tax_classification,
    'entityType': object.entity_type,
    'businessType': object.business_type,
    'stateFormation': object.state_formation,
    'contact': {
      'name': object.contact.name,
      'phone': object.contact.phone,
      'email': object.contact.email
    },
    'agent': {
      'name': object.agent.name,
      'address': object.agent.address
    }
  }
}

Endpoints, Base Url and Client

Each endpoint must have the name of the function that will be invoked as key. The url and method will be used by Axios to create the connection.

The base URL is defined depending on the env:

let baseUrl
if (getEnv().match(/^(development|testing)$/)) {
  baseUrl = apiConstants.default.DEV_BASE_URL
} else {
  baseUrl = getDomain()
}

This allows the API Client to always make requests to the current URL on production and to http://localhost:3001/vapi on development.

Example:

 fetchUserData: {
    url: '/profile',
    method: 'get'
  }

This mean that a certain function inside a service will be named fetchUserData. The connection to API will be done using BASE_URL/profile and the method will be a GET.

The service will be implemented on the data/api/services/api.auth.js and looks like this:

 /**
   * Fetched the userData from the API
   * @param {any} [axiosInstance=client]
   * @returns {Promise}
   */
  fetchUserData (axiosInstance = client) {
    return axiosInstance[endpoints.fetchUserData.method](endpoints.fetchUserData.url, {})
      .then((response) => {
        response.data = _.mapKeys(response.data, (value, key) => {
          return USER_RESPONSE_KEYS[key]
        })
        return Promise.resolve(response.data)
      }).catch((error) => {
        return Promise.reject(error)
      })
  }

Every time a request or response is done, the lodash mapKeys function should be ran with the required model. After the request / response is done, a chain of Promises is activated.

Mock data & New endpoint implementations

All the needed endpoints by now for the application to behave as intended are mocked except the initial request, fetchUserData. All the mocked endpoints are implemented using fake Promises with setTimeout.

Example:

  /**
   * Cancel User's subscription plan
   * @param {any} [axiosInstance=client]
   * @returns {Promise}
   */
  cancelPlan (axiosInstance = client) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve()
      }, 2500)
    })
  }

For new endpoints implementation, just change the fake Promise to a Axios instance API call. Check the example using the fetchUserData service:

Mock

 fetchUserData (axiosInstance = client) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        const response = _.mapKeys(userDataMock, (value, key) => {
          return USER_RESPONSE_KEYS[key]
        })
        resolve(response)
      }, 1000)
    })
  }

Final

  fetchUserData (axiosInstance = client) {
    return axiosInstance[endpoints.fetchUserData.method](endpoints.fetchUserData.url, {})
      .then((response) => {
        response.data = _.mapKeys(response.data, (value, key) => {
          return USER_RESPONSE_KEYS[key]
        })
        return Promise.resolve(response.data)
      }).catch((error) => {
        return Promise.reject(error)
      })
   }

How to call API services

By now, all the Documents & Account Details API services are already being called. For future reference, there is only 2 ways to correctly call this services:

  • Actions - The API call request answer will be used to manipulate something on store
  • Components - The API call request answer doesn't matter to the application's store and the service can be directly invoked inside a component

Project Architecture

Setup

Application Libraries:

Scaffolding:

Package and Modules

Lint

Tests

Clone this wiki locally