This answer to a similar question does a great job at explaining how fastify-plugin
works and what it does. After reading the explanation, I still have a question remaining; how is this different from a normal function call instead of using the .register()
method?
To clarify with an example, how are the two approaches below different from each other:
const app = fastify();
// Register a fastify-plugin that decorates app
const myPlugin = fp((app: FastifyInstance) => {
app.decorate('example', 10);
});
app.register(myPlugin);
// Just decorate the app directly
const decorateApp = (app: FastifyInstance) => {
app.decorate('example', 10);
};
decorateApp(app);
Advertisement
Answer
By writing a decorateApp
function you are creating your own “API” to load your application.
That said, the first burden you will face soon is sync or async:
- decorateApp is a sync function
- decorateAppAsync within an async function
For example, you need to preload something from the database before you can start your application.
const decorateApp = (app) => {
app.register(require('@fastify/mongodb'))
};
const businessLogic = async (app) => {
const data = await app.mongo.db.collection('data').find({}).toArray()
}
decorateApp(app)
businessLogic(app) // whoops: it is async
In this example you need to change a lot of code:
- the
decorateApp
function must be async - the mongodb registration must be awaited
- the main code that loads the application must be async
Instead, by using the fastify’s approach, you need to update only the plugin that loads the database:
const applicationConfigPlugin = fp(
+ async function (fastify) {
- function (fastify, opts, next) {
- app.register(require('@fastify/mongodb'))
- next()
+ await app.register(require('@fastify/mongodb'))
}
)
PS: note that fastify-plugin example code misses the
next
callback since it is a sync function.
The next bad pattern will be high hidden coupling between functions.
Every application needs a config
. Usually, the fastify instance is decorated with it.
So, you will have something like:
decorateAppWithConfig(app);
decorateAppWithSomethingElse(app);
Now, decorateAppWithSomethingElse
will need to know that it is loaded after decorateAppWithConfig
.
Instead, by using the fastify-plugin
, you can write:
const applicationConfigPlugin = fp(
async function (fastify) {
fastify.decorate('config', 42);
},
{
name: 'my-app-config',
}
)
const applicationBusinessLogic = fp(
async function (fastify) {
// ...
},
{
name: 'my-app-business-logic',
dependencies: ['my-app-config']
}
)
// note that the WRONG order of the plugins
app.register(applicationBusinessLogic);
app.register(applicationConfigPlugin);
Now, you will get a nice error, instead of a Cannot read properties of undefined
when the config
decorator is missing:
AssertionError [ERR_ASSERTION]: The dependency 'my-app-config' of plugin 'my-app-business-logic' is not registered
So, basically writing a series of functions that use/decorate the fastify instance is doable but it adds
a new convention to your code that will have to manage the loading of the plugins.
This job is already implemented by fastify and the fastify-plugin
adds many validation checks to it.
So, by considering the question’s example: there is no difference, but using that approach to a bigger application will lead to a more complex code:
- sync/async loading functions
- poor error messages
- hidden dependencies instead of explicit ones