Skip to main content

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.

front-hub.config.js
module.exports = {
// ...
services: [
{
name: 'service-name',
rootDir: '<rootDir>/fake-api',
endpoints: {
production: 'https://api.my-project.com',
staging: 'https://api-staging.my-project.com',
},
},
],
// ...
}
info

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:

my-monorepo/packages/my-microfrontend/fake-api/users.js
// patch, put, etc..
export get = () => {};
export post = () => {};
export _delete = () => {};

// extra method
export collection = () => {};

export { get, post, _delete as delete, collection };
info

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:

my-monorepo/packages/my-microfrontend/fake-api/healthcheck.js
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.

my-monorepo/packages/my-microfrontend/fake-api/users.js
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:

my-monorepo/packages/my-microfrontend/fake-api/users.js
export const get = (req, res, _, { db }) => {
const result = db.get('collection-name')
res.json(result.value())
}
note

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:

my-monorepo/packages/my-microfrontend/fake-api/users.js
// 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:

front-hub.config.js
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:

my-monorepo/packages/my-microfrontend/fake-api/users.js
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 }
}