1

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);
Markus Meskanen
  • 19,939
  • 18
  • 80
  • 119

1 Answers1

2

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
Manuel Spigolon
  • 11,003
  • 5
  • 50
  • 73
  • Sorry but this doesn't answer my question in any form. I understand the difference between `fastify-plugin` and normal fastify plugins, but my question was how is `fastfy-plugin` different from just using a normal TypeScript decorate function over the fastify's `.decorate()` method? Notice the difference in calling: `app.register(fp(plugin))` vs `plugin(app)` – Markus Meskanen Jun 09 '22 at 04:14
  • I will retry: it changes **where** the decorator is attached, so the parent context or the new context – Manuel Spigolon Jun 09 '22 at 07:32
  • That still makes no sense, did you read my code example? I'm not talking normal Fastify plugins vs `fp()` decorated plugins, I'm talking `fp()` decorated plugins vs "not a plugin at all but a simple function that I give the app to as an argument". – Markus Meskanen Jun 10 '22 at 10:15
  • Notice how my second code example never uses the `app.register()` method. – Markus Meskanen Jun 10 '22 at 10:16
  • Are you focused on decorator only or all the fastify's features (hooks, error handler, register as well)? based on that, there is a huge difference regarding how the code is loaded (basically you are writing more code) – Manuel Spigolon Jun 10 '22 at 14:07
  • I mean I don't know, that's why I'm asking here. – Markus Meskanen Jun 11 '22 at 15:49
  • I have edited the answer, please check it – Manuel Spigolon Jun 12 '22 at 07:36
  • That perfectly answers my question, sorry for the trouble and thank you for the answer! – Markus Meskanen Jun 13 '22 at 08:26