ForrestJS API » ForrestJS.run()
ForrestJS API » ForrestJS.run()
ForrestJS.run()
The Philosopy
You can organize your business logic into a ForrestJS App in order to maximize the Single Responsibility and Open/Close principle. And yes, also to maximize code reusability.
You should divide your source files in two categories:
- Features
- Services
Features
A Feature is what your customer is willing to pay for.
It's custom made and SHOULD NOT BE SHARED among different Apps.
It most certainly contains business secrets.
✅ Some examples of Features are:
- A server-side rendered home page
- An endpoint that list your users from a database
- An authentication service that builds JWT tokens
Services
A Service is everything that is irrelevant to your customer, but absolutely needed in order to run your Features.
Services are generic and SHOULD BE REUSABLE across different Apps.
Services belong to NPM or your private version of it.
✅ Some examples of Services are:
- A function that performs an SQL query towards a DBMS
- An ApolloClient instance that let you fetch from a GraphQL server
- A component library for React (like MUI)
- A routing library for React (like react-router)
Quick Start for NodeJS
You can run a simple Fastify app with custom routing and serve a complex website or REST / GraphQL API:
Quick Start for React
You can render a React App and use the ForrestJS compositional approach in your frontend.
Intro
A ForrestJS App is heavily inspired to a React app:
- it receives some properties
- it has an internal context
- it has a (boot) lifecycle
- it produces a predictable result
[[TO BE CONTINUED]]
ForrestJS.run() API
services
type: Array
features
type: Array
settings
type: Object | (async) Function
Pass configuration to your running App.
Any key that you set into this object will be available to any Service, Feature, and registered Action via getConfig()
API.
Settings can be calculated on the fly by passing an async Function that will receive the full App context (it is indeed just a registed Action):
forrest.run({
settings: async ({ setConfig }) => {
// Get remote data:
const services = await getServiceDiscoveryInfo();
// Use the API to extend the configuration:
setConfg('service.discovery.info', services);
},
});
context
type: Object
Add dependencies to your runing App.
Any key that you set into this object will be available to any Service, Feature, and registered Action via getContext()
API.
trace
type: String
value: "compact" | "full"
App Boot Lifecycle in ForrestJS
The Boot Lifecycle is composed by a list of asynchronous Extensions created by ForrestJS during the booting of your App.
// Only available to Services
$START serie
$SETTINGS serie
// Availabe to Services and Features
$INIT_SERVICES parallel
$INIT_SERVICE serie
$INIT_FEATURES parallel
$INIT_FEATURE serie
$START_SERVICES parallel
$START_SERVICE serie
$START_FEATURES parallel
$START_FEATURE serie
$FINISH serie
👉 Each lifecycle Extension provides the App's Context as first parameter into the Action's handler.
registerAction({
target: '$INIT_FEATURE',
handler: (ctx) => {
ctx.getConfig('foo.bar');
},
});
🔥 All the lifecycle Extensions are ASYNCHRONOUS.
👉 You can use async/await
in your Action's handlers, or return promises:
// Assuming you are running `service-pg`
registerAction({
target: '$START_FEATURE',
handler: async ({ getContext }) => {
const query = getContext('pg.query');
await query('SELECT NOW()');
},
});
Action Execution Order
In the rare case in which you REALLY need to control the execution order of a specific Lifecycle Action, please target the singular Extension: $INIT_FEATURE
, $START_SERVICE
.
👉 When you run Actions in sync
, serie
, or waterfall
, the priority makes perfect sense as the Actions are executed one AFTER another.
🔥 When uou run Actions in parallel
, the priority is only used to set the launch order of the handlers. The execution order is entirely unpredictable for it is asynchronous.