Fake API
FrontHub automates all boring stuff we need to do while dealing with REST APIs, things like mocking, documenting API requests, are done for you as you are developing.
To do that, FrontHub require you to follow simple conventions but not forgetting of giving you a lot of power while developing.
Configuration
Edit your front-hub.config.js file adding the configuration below:
One endpoint for production and one for staging. To know how to configure more than one endpoint check the Using two or more APIs section.
module.exports = {
  // ...
  services: [
    {
      name: 'service-name',
      rootDir: '<rootDir>/fake-api',
      endpoints: {
        production: 'https://api.my-project.com',
        staging: 'https://api-staging.my-project.com',
      },
    },
  ],
  // ...
}
The <rootDir> is a variable for FrontHub to consider the root of the micro frontend, don't replace the variable, use it exactly as shown in the example.
What you can change is the fake-api directory to whatever you like, like the name of the API itself (i.e. "checkout-api" or "auth-api"), but it should reflect the name of the directory you will create.
In development environment, FrontHub will use the fake endpoint using the http://localhost:5000/service-name.
In staging environment, FrontHub will use the services[].endpoints.staging endpoint defined in front-hub.config.js.
In production environment it will use the services[].endpoints.production endpoint defined in front-hub.config.js.
If you are using monorepo then consider that your <rootDir> is each directory inside of packages. In the example below the <rootDir> is the my-microfrontend folder and inside it's fake-api directory:
├── my-monorepo/
│   ├── packages/
│     └── my-microfrontend
│       └── fake-api/
│         └── users.js
│         └── leads.js
│         └── ...
Following the example above, we have one Fake API called fake-api with two endpoints, /users and /leads. This endpoints will be exposed in your url based on the service name:
// in development:
http://localhost:5000/service-name/users
http://localhost:5000/service-name/leads
// in production:
https://api.my-project.com/users
https://api.my-project.com/leads
Don't worry about changing the base URL, FrontHub already handles it internally for you 😉.
HTTP Request Methods
Create methods representing the HTTP request methods. Example:
// patch, put, etc..
export get = () => {};
export post = () => {};
export _delete = () => {};
// extra method
export collection = () => {};
export { get, post, _delete as delete, collection };
As delete is a reserved word in JavaScript, you will need to give this method function another name (_delete in the example) and then do a named export.
A basic example of an endpoint:
export const get = (req, res) => {
  res.json({
    database: 'ok',
    redis: 'ok',
  })
}
Collection Method
We offer the extra method called collection used to generate data with faker.js, note it is optional.
You should return an object which contains the name and schema property.
export const collection = () => {
  return {
    // Used to identify a collection
    name: 'collection-name',
    // Schema to generate the collection
    schema: {
      type: 'array',
      // An array between 100 and 200 items
      // will be randomly generated
      minItems: 100,
      maxItems: 200,
      items: {
        type: 'object',
        // You can use all the faker api methods available.
        // Check the documentation for more information: https://fakerjs.dev/
        properties: {
          user: {
            type: 'string',
            faker: 'internet.userName',
          },
          email: {
            type: 'string',
            faker: 'internet.email',
          },
        },
        required: ['user', 'email'],
      },
    },
  }
}
Then you can use the collection name to generate a custom response:
export const get = (req, res, _, { db }) => {
  const result = db.get('collection-name')
  res.json(result.value())
}
The prop schema is a JSON Schema. Check if your schema is valid here.
You can use the request query param to filter the response data:
// get all data of collection and filter by email if is not empty
export const get = (req, res, _, { db }) => {
  const { email } = req.query
  const result = db
    .get('collection-name')
    .filter(user => (email ? user.email === email : user.email))
  res.json(result.value())
}
Using folders
You can use folder to represent routes in your API endpoint. Let's use an example following the same service configuration above.
├── fake-api/
│   ├── leads/
│     └── :id/
│       └── conversions/
│         └── types.js
│         └── ...
With this structure we have the following route /leads/:id/conversions/types
You can get the :id accessing request.param.
export const get = (request, response) => {
  const { id } = request.params
  result = db.get('collection-name').filter(user => user.id === id)
  response.json(result.value())
}
Using two or more APIs
You can create a configuration for each API that your microfrontend connects to. For example, suppose you have an analytics API and a leads API, so the configuration would look like this:
module.exports = {
  // ...
  services: [
    {
      name: 'analytics-api',
      rootDir: '<rootDir>/fake-analytics-api',
      endpoints: {
        production: 'https://api.analytics.com',
        staging: 'https://api-staging.analytics.com',
      },
    },
    {
      name: 'leads-api',
      rootDir: '<rootDir>/fake-leads-api',
      endpoints: {
        production: 'https://leads.com/api',
        staging: 'https://leads-staging.com/api',
      },
    },
  ],
  // ...
}
The directories must follow the configuration made in the rootDir property:
├── my-monorepo/
│   ├── packages/
│     └── my-microfrontend
│       └── fake-analytics-api/
│         └── dashboard.js
│         └── ...
│       └── fake-leads-api/
│         └── leads.js
│         └── ...
Running the service
The CLI (front-hub) provides the commands necessary for run your fake API:
front-hub fake api
For projects which does not use Lerna:
front-hub fake api --noLerna
You can pass an optional port number:
front-hub fake api -p 9000
For more details on the command see the API documentation.
Client
The client is just a wrapper to Axios.
You need to get the client function to connect with the service API:
import { useState, useEffect } from 'react'
import { client } from '@resultadosdigitais/front-hub/http'
/**
 * Put the creation of the client in a function or class
 * so that it is created on demand
 */
const api = () => {
  // Returns an axios instance
  return client.create('service-name')
}
const App = () => {
  // Give preference to userReducer to handle more complete request state,
  // or use something ready-made like React Query
  const [leads, setLeads] = useState([])
  useEffect(() => {
    api()
      .get('/leads')
      .then(response => {
        setLeads(response.data)
      })
  }, [])
  return (
    <ul>
      {leads.map(lead => (
        <li key={lead.id}>{lead.name}</li>
      ))}
    </ul>
  )
}
The client function receives two arguments: the service name and an optional configuration object which is passed directly to the Axios.create:
import { client } from '@resultadosdigitais/front-hub/http'
const api = () => {
  // The configuration object is of type AxiosRequestConfig
  return client.create('service-name', {
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json',
      'X-CSRF-TOKEN': 'the_token',
    },
  })
}
export default api
You can use all Axios request method aliases for http connections.
Examples
Using headers
You can do anything you would do on an HTTP route, such as setting request and response headers for pagination, for example:
export const collection = () => {
  return {
    name: 'users',
    schema: {
      type: 'array',
      minItems: 10,
      maxItems: 100,
      items: {
        type: 'object',
        properties: {
          name: {
            type: 'string',
            faker: 'internet.userName',
          },
          email: {
            type: 'string',
            faker: 'internet.email',
          },
        },
        required: ['user', 'email'],
      },
    },
  }
}
export const get = (req, res, _, { db }) => {
  const result = db.get('users')
  const entities = result.value()
  // Get info from request headers
  const page = req.headers['pagination-page'] || 1
  const perPage = req.headers['pagination-per-page'] || 10
  const startIndex = (page - 1) * perPage
  const endIndex = startIndex + perPage
  const users = entities.slice(startIndex, endIndex)
  const size = users.length
  // Set info into response headers
  res.setHeader('pagination-page', page)
  res.setHeader('pagination-per-page', perPage)
  res.setHeader('pagination-page-size', size)
  res.setHeader('pagination-total', entities.length)
  // Expose all new headers
  res.setHeader('Access-Control-Expose-Headers', '*')
  res.json({ users })
}
In the client you can fetch like this:
import { client } from '@resultadosdigitais/front-hub/http'
const api = () => {
  // Change 'service-name' with your Fake API name
  return client.create('service-name')
}
const parseHeaders = headers => ({
  getHeaderInt: key => {
    const result = parseInt(headers[key], 10)
    return isNaN(result) ? 0 : result
  },
})
const getUsers = async () => {
  const { data, headers } = await api().get('/users')
  const { getHeaderInt } = parseHeaders(headers)
  const pagination = {
    page: getHeaderInt('pagination-page'),
    perPage: getHeaderInt('pagination-per-page'),
    pageSize: getHeaderInt('pagination-page-size'),
    total: getHeaderInt('pagination-total'),
  }
  return { users: data.users, pagination }
}