Skip to main content

Package Development

Formidable provides an easy way of letting developers extend the framework. This is a great way of building in features that aren't bundled with the framework. For example, the Pretty Errors package adds a new view that gets displayed when there's an error. This view only gets triggered if the application is in debug mode.

Package Registration

Service Resolver

We can create a service resolver in our package that adds a route that returns a random quote:

src/QuotesServiceResolver.ts
import { Route } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'

export class QuotesServiceResolver extends ServiceResolver {
boot(): void {
Route.get('quote', () => {
const key = Math.floor(Math.random() * (2 - 1 + 1))

this.quotes[key] ?? 'Could not find a quote'
})
}

get quotes(): Array<string> {
return [
'Computers are good at following instructions, but not at reading your mind.'
'UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity.'
]
}

We can then register the resolver in a formidable application in the bootstrap/resolvers.imba or bootstrap/resolvers.ts config file:

bootstrap/resolvers.ts
import { QuotesServiceResolver } from '<custom-package>'
...

export default [
...
QuotesServiceResolver,

Once the resolver has been registered, you can access /quote in the browser, and you will see any of the 2 quotes you added above.

Publisher

Before a package can be used by Formidable, it needs to be registered. This means a package can be easily turned off and removed from the application boot cycle.

You can ship framework files such as a config file, along with your package. This can be done by including a Package.js file in your package:

formidable/Package.js
exports.Package = class Package {
publish(language = 'imba') {
const ext = language.toLowerCase() == 'imba'
? 'imba' : (
language.toLowerCase() == 'typescript' ? 'ts' : 'imba'
)

const configKey = `config/bugsnag.${ext}`;
const configValue = `./formidable/config/bugsnag.${ext}`;

return {
config: {
paths: {
[configKey]: configValue
}
}
}
}
}

For formidable to be aware of the Package.js file, you must include it in the package.json npm file with the key publisher:

package.json
{
"name": "my-package",
"version": "1.0.0",
"publisher": "formidable/Package.js",
...

The Package.js file contains a publish function which should always return an object. In this object, you can specify the files that must be copied from the package to the framework.

e.g. The package above will copy the formidable/config/bugsnag.imba or formidable/config/bugsnag.ts file from the package to config folder in a formidable application when running the package:publish Craftsman command:

node craftsman package:publish --package=<package-name> --tag=config
flagdescription
--packagenpm package that should be published.
--tagtags that should be published - paths returned by the publish function

Server

Hooks

Formidable supports Fastify hooks, in the example below, we will look at how you can register a hook in your service resolver:

import { Log } from '@formidablejs/logger'
import { FastifyRequest } from '@formidablejs/framework'
import { ServiceResolver } from '@formidablejs/framework'

export class HttpLoggerServiceResolver extends ServiceResolver {
boot(): void {
this.app.addHook('onRequest', (request: FastifyRequest) => {
Log.info("{request.method}: {request.url}")
})
}

This hook logs all requests made to our application.

For a list of fastify hooks you can use: visit Fastify Hooks.

Plugins

We can also register Fastify plugins:

Install under-pressure:

npm i under-pressure --save

Update your Service Resolver:

import { ServiceResolver } from '@formidablejs/framework'

export class UnderPresureServiceResolver extends ServiceResolver {
get config(): object {
return {
maxEventLoopDelay: 1000,
maxHeapUsedBytes: 100000000,
maxRssBytes: 100000000,
maxEventLoopUtilization: 0.98
}
}

boot(): void {
this.app.register(require('under-pressure'), this.config)
}
}

If you want to tinker with the updated Fastify server instance:

import { ServiceResolver } from '@formidablejs/framework'
import { FastifyInstance } from '@formidablejs/framework'

export class UnderPresureServiceResolver extends ServiceResolver {
get config(): object {
return {
maxEventLoopDelay: 1000,
maxHeapUsedBytes: 100000000,
maxRssBytes: 100000000,
maxEventLoopUtilization: 0.98
}
}

boot(): void {
this.app.register(require('under-pressure'), this.config, (error, instance: FastifyInstance) => {
if (error) {
throw error
}

// do something with the instance
}
}
}

For a list of fastify plugins you can use: visit Fastify Ecosystem.

Routes

To register a new route, just use the Route class:

import { ServiceResolver } from '@formidablejs/framework'
import { Route } from '@formidablejs/framework'
import { view } from '@formidablejs/framework'
import { Dashboard } from '../views/Dashboard'

export class DashboardServiceResolver extends ServiceResolver {
boot(): void {
Route.get('dashboard', () => view(Dashboard))
}
}

In the example above, the Service Resolver adds a new dashboard GET route.

See Routing for more information.

Commands

You can register a package command using the registerCommand app function:

import { ServiceResolver } from '@formidablejs/framework'
import { MakeRoleCommand } from '../commands/MakeRoleCommand'

export class DashboardServiceResolver extends ServiceResolver {
boot(): void {
this.app.registerCommand(MakeRoleCommand)
}
}

See Commands for more information.