CoolFace
Apppublic

strong-tie/inbound-calls

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
Plugins-Guide.md521 linesDownload Raw Back to Guides
1<h1 align="center">Fastify</h1>2 3# The hitchhiker's guide to plugins4First of all, `DON'T PANIC`!5 6Fastify was built from the beginning to be an extremely modular system. We built7a powerful API that allows you to add methods and utilities to Fastify by8creating a namespace. We built a system that creates an encapsulation model,9which allows you to split your application into multiple microservices at any10moment, without the need to refactor the entire application.11 12**Table of contents**13- [The hitchhiker's guide to plugins](#the-hitchhikers-guide-to-plugins)14  - [Register](#register)15  - [Decorators](#decorators)16  - [Hooks](#hooks)17  - [How to handle encapsulation and18    distribution](#how-to-handle-encapsulation-and-distribution)19  - [ESM support](#esm-support)20  - [Handle errors](#handle-errors)21  - [Custom errors](#custom-errors)22  - [Emit Warnings](#emit-warnings)23  - [Let's start!](#lets-start)24 25## Register26<a id="register"></a>27 28As with JavaScript, where everything is an object, in Fastify everything is a29plugin.30 31Your routes, your utilities, and so on are all plugins. To add a new plugin,32whatever its functionality may be, in Fastify you have a nice and unique API:33[`register`](../Reference/Plugins.md).34```js35fastify.register(36  require('./my-plugin'),37  { options }38)39```40`register` creates a new Fastify context, which means that if you perform any41changes on the Fastify instance, those changes will not be reflected in the42context's ancestors. In other words, encapsulation!43 44*Why is encapsulation important?*45 46Well, let's say you are creating a new disruptive startup, what do you do? You47create an API server with all your stuff, everything in the same place, a48monolith!49 50Ok, you are growing very fast and you want to change your architecture and try51microservices. Usually, this implies a huge amount of work, because of cross52dependencies and a lack of separation of concerns in the codebase.53 54Fastify helps you in that regard. Thanks to the encapsulation model, it will55completely avoid cross dependencies and will help you structure your code into56cohesive blocks.57 58*Let's return to how to correctly use `register`.*59 60As you probably know, the required plugins must expose a single function with61the following signature62```js63module.exports = function (fastify, options, done) {}64```65Where `fastify` is the encapsulated Fastify instance, `options` is the options66object, and `done` is the function you **must** call when your plugin is ready.67 68Fastify's plugin model is fully reentrant and graph-based, it handles69asynchronous code without any problems and it enforces both the load and close70order of plugins. *How?* Glad you asked, check out71[`avvio`](https://github.com/mcollina/avvio)! Fastify starts loading the plugin72__after__ `.listen()`, `.inject()` or `.ready()` are called.73 74Inside a plugin you can do whatever you want, register routes, utilities (we75will see this in a moment) and do nested registers, just remember to call `done`76when everything is set up!77```js78module.exports = function (fastify, options, done) {79  fastify.get('/plugin', (request, reply) => {80    reply.send({ hello: 'world' })81  })82 83  done()84}85```86 87Well, now you know how to use the `register` API and how it works, but how do we88add new functionality to Fastify and even better, share them with other89developers?90 91## Decorators92<a id="decorators"></a>93 94Okay, let's say that you wrote a utility that is so good that you decided to95make it available along with all your code. How would you do it? Probably96something like the following:97```js98// your-awesome-utility.js99module.exports = function (a, b) {100  return a + b101}102```103```js104const util = require('./your-awesome-utility')105console.log(util('that is ', 'awesome'))106```107Now you will import your utility in every file you need it in. (And do not108forget that you will probably also need it in your tests).109 110Fastify offers you a more elegant and comfortable way to do this, *decorators*.111Creating a decorator is extremely easy, just use the112[`decorate`](../Reference/Decorators.md) API:113```js114fastify.decorate('util', (a, b) => a + b)115```116Now you can access your utility just by calling `fastify.util` whenever you need117it - even inside your test.118 119And here starts the magic; do you remember how just now we were talking about120encapsulation? Well, using `register` and `decorate` in conjunction enable121exactly that, let me show you an example to clarify this:122```js123fastify.register((instance, opts, done) => {124  instance.decorate('util', (a, b) => a + b)125  console.log(instance.util('that is ', 'awesome'))126 127  done()128})129 130fastify.register((instance, opts, done) => {131  console.log(instance.util('that is ', 'awesome')) // This will throw an error132 133  done()134})135```136Inside the second register call `instance.util` will throw an error because137`util` exists only inside the first register context.138 139Let's step back for a moment and dig deeper into this: every time you use the140`register` API, a new context is created which avoids the negative situations141mentioned above.142 143Do note that encapsulation applies to the ancestors and siblings, but not the144children.145```js146fastify.register((instance, opts, done) => {147  instance.decorate('util', (a, b) => a + b)148  console.log(instance.util('that is ', 'awesome'))149 150  fastify.register((instance, opts, done) => {151    console.log(instance.util('that is ', 'awesome')) // This will not throw an error152    done()153  })154 155  done()156})157 158fastify.register((instance, opts, done) => {159  console.log(instance.util('that is ', 'awesome')) // This will throw an error160 161  done()162})163```164*Take home message: if you need a utility that is available in every part of165your application, take care that it is declared in the root scope of your166application. If that is not an option,  you can use the `fastify-plugin` utility167as described [here](#distribution).*168 169`decorate` is not the only API that you can use to extend the server170functionality, you can also use `decorateRequest` and `decorateReply`.171 172*`decorateRequest` and `decorateReply`? Why do we need them if we already have173`decorate`?*174 175Good question, we added them to make Fastify more developer-friendly. Let's see176an example:177```js178fastify.decorate('html', payload => {179  return generateHtml(payload)180})181 182fastify.get('/html', (request, reply) => {183  reply184    .type('text/html')185    .send(fastify.html({ hello: 'world' }))186})187```188It works, but it could be much better!189```js190fastify.decorateReply('html', function (payload) {191  this.type('text/html') // This is the 'Reply' object192  this.send(generateHtml(payload))193})194 195fastify.get('/html', (request, reply) => {196  reply.html({ hello: 'world' })197})198```199Reminder that the `this` keyword is not available on *arrow functions*,200so when passing functions in *`decorateReply`* and *`decorateRequest`* as201a utility that also needs access to the `request` and `reply` instance,202a function that is defined using the `function` keyword is needed instead203of an *arrow function expression*.204 205In the same way you can do this for the `request` object:206```js207fastify.decorate('getHeader', (req, header) => {208  return req.headers[header]209})210 211fastify.addHook('preHandler', (request, reply, done) => {212  request.isHappy = fastify.getHeader(request.raw, 'happy')213  done()214})215 216fastify.get('/happiness', (request, reply) => {217  reply.send({ happy: request.isHappy })218})219```220Again, it works, but it can be much better!221```js222fastify.decorateRequest('setHeader', function (header) {223  this.isHappy = this.headers[header]224})225 226fastify.decorateRequest('isHappy', false) // This will be added to the Request object prototype, yay speed!227 228fastify.addHook('preHandler', (request, reply, done) => {229  request.setHeader('happy')230  done()231})232 233fastify.get('/happiness', (request, reply) => {234  reply.send({ happy: request.isHappy })235})236```237 238We have seen how to extend server functionality and how to handle the239encapsulation system, but what if you need to add a function that must be240executed whenever the server "[emits](../Reference/Lifecycle.md)" an241event?242 243## Hooks244<a id="hooks"></a>245 246You just built an amazing utility, but now you need to execute that for every247request, this is what you will likely do:248```js249fastify.decorate('util', (request, key, value) => { request[key] = value })250 251fastify.get('/plugin1', (request, reply) => {252  fastify.util(request, 'timestamp', new Date())253  reply.send(request)254})255 256fastify.get('/plugin2', (request, reply) => {257  fastify.util(request, 'timestamp', new Date())258  reply.send(request)259})260```261I think we all agree that this is terrible. Repeated code, awful readability and262it cannot scale.263 264So what can you do to avoid this annoying issue? Yes, you are right, use a265[hook](../Reference/Hooks.md)!266 267```js268fastify.decorate('util', (request, key, value) => { request[key] = value })269 270fastify.addHook('preHandler', (request, reply, done) => {271  fastify.util(request, 'timestamp', new Date())272  done()273})274 275fastify.get('/plugin1', (request, reply) => {276  reply.send(request)277})278 279fastify.get('/plugin2', (request, reply) => {280  reply.send(request)281})282```283Now for every request, you will run your utility. You can register as many hooks284as you need.285 286Sometimes you want a hook that should be executed for just a subset of routes,287how can you do that? Yep, encapsulation!288 289```js290fastify.register((instance, opts, done) => {291  instance.decorate('util', (request, key, value) => { request[key] = value })292 293  instance.addHook('preHandler', (request, reply, done) => {294    instance.util(request, 'timestamp', new Date())295    done()296  })297 298  instance.get('/plugin1', (request, reply) => {299    reply.send(request)300  })301 302  done()303})304 305fastify.get('/plugin2', (request, reply) => {306  reply.send(request)307})308```309Now your hook will run just for the first route!310 311An alternative approach is to make use of the [onRoute hook](../Reference/Hooks.md#onroute)312to customize application routes dynamically from inside the plugin. Every time313a new route is registered, you can read and modify the route options. For example,314based on a [route config option](../Reference/Routes.md#routes-options):315 316```js317fastify.register((instance, opts, done) => {318  instance.decorate('util', (request, key, value) => { request[key] = value })319 320  function handler(request, reply, done) {321    instance.util(request, 'timestamp', new Date())322    done()323  }324 325  instance.addHook('onRoute', (routeOptions) => {326    if (routeOptions.config && routeOptions.config.useUtil === true) {327      // set or add our handler to the route preHandler hook328      if (!routeOptions.preHandler) {329        routeOptions.preHandler = [handler]330        return331      }332      if (Array.isArray(routeOptions.preHandler)) {333        routeOptions.preHandler.push(handler)334        return335      }336      routeOptions.preHandler = [routeOptions.preHandler, handler]337    }338  })339 340  fastify.get('/plugin1', {config: {useUtil: true}}, (request, reply) => {341    reply.send(request)342  })343 344  fastify.get('/plugin2', (request, reply) => {345    reply.send(request)346  })347 348  done()349})350```351 352This variant becomes extremely useful if you plan to distribute your plugin, as353described in the next section.354 355As you probably noticed by now, `request` and `reply` are not the standard356Node.js *request* and *response* objects, but Fastify's objects.357 358 359## How to handle encapsulation and distribution360<a id="distribution"></a>361 362Perfect, now you know (almost) all of the tools that you can use to extend363Fastify. Nevertheless, chances are that you came across one big issue: how is364distribution handled?365 366The preferred way to distribute a utility is to wrap all your code inside a367`register`. Using this, your plugin can support asynchronous bootstrapping368*(since `decorate` is a synchronous API)*, in the case of a database connection369for example.370 371*Wait, what? Didn't you tell me that `register` creates an encapsulation and372that the stuff I create inside will not be available outside?*373 374Yes, I said that. However, what I didn't tell you is that you can tell Fastify375to avoid this behavior with the376[`fastify-plugin`](https://github.com/fastify/fastify-plugin) module.377```js378const fp = require('fastify-plugin')379const dbClient = require('db-client')380 381function dbPlugin (fastify, opts, done) {382  dbClient.connect(opts.url, (err, conn) => {383    fastify.decorate('db', conn)384    done()385  })386}387 388module.exports = fp(dbPlugin)389```390You can also tell `fastify-plugin` to check the installed version of Fastify, in391case you need a specific API.392 393As we mentioned earlier, Fastify starts loading its plugins __after__394`.listen()`, `.inject()` or `.ready()` are called and as such, __after__ they395have been declared. This means that, even though the plugin may inject variables396to the external Fastify instance via [`decorate`](../Reference/Decorators.md),397the decorated variables will not be accessible before calling `.listen()`,398`.inject()` or `.ready()`.399 400In case you rely on a variable injected by a preceding plugin and want to pass401that in the `options` argument of `register`, you can do so by using a function402instead of an object:403```js404const fastify = require('fastify')()405const fp = require('fastify-plugin')406const dbClient = require('db-client')407 408function dbPlugin (fastify, opts, done) {409  dbClient.connect(opts.url, (err, conn) => {410    fastify.decorate('db', conn)411    done()412  })413}414 415fastify.register(fp(dbPlugin), { url: 'https://example.com' })416fastify.register(require('your-plugin'), parent => {417  return { connection: parent.db, otherOption: 'foo-bar' }418})419```420In the above example, the `parent` variable of the function passed in as the421second argument of `register` is a copy of the **external Fastify instance**422that the plugin was registered at. This means that we can access any423variables that were injected by preceding plugins in the order of declaration.424 425## ESM support426<a id="esm-support"></a>427 428ESM is supported as well from [Node.js429`v13.3.0`](https://nodejs.org/api/esm.html) and above! Just export your plugin430as an ESM module and you are good to go!431 432```js433// plugin.mjs434async function plugin (fastify, opts) {435  fastify.get('/', async (req, reply) => {436    return { hello: 'world' }437  })438}439 440export default plugin441```442 443## Handle errors444<a id="handle-errors"></a>445 446One of your plugins may fail during startup. Maybe you expect it447and you have a custom logic that will be triggered in that case. How can you448implement this? The `after` API is what you need. `after` simply registers a449callback that will be executed just after a register, and it can take up to450three parameters.451 452The callback changes based on the parameters you are giving:453 4541. If no parameter is given to the callback and there is an error, that error455   will be passed to the next error handler.4561. If one parameter is given to the callback, that parameter will be the error457   object.4581. If two parameters are given to the callback, the first will be the error459   object; the second will be the done callback.4601. If three parameters are given to the callback, the first will be the error461   object, the second will be the top-level context unless you have specified462   both server and override, in that case, the context will be what the override463   returns, and the third the done callback.464 465Let's see how to use it:466```js467fastify468  .register(require('./database-connector'))469  .after(err => {470    if (err) throw err471  })472```473 474## Custom errors475<a id="custom-errors"></a>476 477If your plugin needs to expose custom errors, you can easily generate consistent478error objects across your codebase and plugins with the479[`@fastify/error`](https://github.com/fastify/fastify-error) module.480 481```js482const createError = require('@fastify/error')483const CustomError = createError('ERROR_CODE', 'message')484console.log(new CustomError())485```486 487## Emit Warnings488<a id="emit-warnings"></a>489 490If you want to deprecate an API, or you want to warn the user about a specific491use case, you can use the492[`process-warning`](https://github.com/fastify/process-warning) module.493 494```js495const warning = require('process-warning')()496warning.create('MyPluginWarning', 'MP_ERROR_CODE', 'message')497warning.emit('MP_ERROR_CODE')498```499 500## Let's start!501<a id="start"></a>502 503Awesome, now you know everything you need to know about Fastify and its plugin504system to start building your first plugin, and please if you do, tell us! We505will add it to the [*ecosystem*](https://github.com/fastify/fastify#ecosystem)506section of our documentation!507 508If you want to see some real-world examples, check out:509- [`@fastify/view`](https://github.com/fastify/point-of-view) Templates510  rendering (*ejs, pug, handlebars, marko*) plugin support for Fastify.511- [`@fastify/mongodb`](https://github.com/fastify/fastify-mongodb) Fastify512  MongoDB connection plugin, with this you can share the same MongoDB connection513  pool in every part of your server.514- [`@fastify/multipart`](https://github.com/fastify/fastify-multipart) Multipart515  support for Fastify516- [`@fastify/helmet`](https://github.com/fastify/fastify-helmet) Important517  security headers for Fastify518 519 520*Do you feel like something is missing here? Let us know! :)*521