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 }
}