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