Skip to content
Advertisement

What’s the advantage of fastify-plugin over a normal function call?

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
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement