The hitchhiker's guide to plugins | Fastify (2024)

First of all, DON'T PANIC!

Fastify was built from the beginning to be an extremely modular system. We builta powerful API that allows you to add methods and utilities to Fastify bycreating a namespace. We built a system that creates an encapsulation model,which allows you to split your application into multiple microservices at anymoment, without the need to refactor the entire application.

Table of contents

  • The hitchhiker's guide to plugins
    • Register
    • Decorators
    • Hooks
    • How to handle encapsulation anddistribution
    • ESM support
    • Handle errors
    • Custom errors
    • Emit Warnings
    • Let's start!

Register

As with JavaScript, where everything is an object, in Fastify everything is aplugin.

Your routes, your utilities, and so on are all plugins. To add a new plugin,whatever its functionality may be, in Fastify you have a nice and unique API:register.

fastify.register(
require('./my-plugin'),
{ options }
)

register creates a new Fastify context, which means that if you perform anychanges on the Fastify instance, those changes will not be reflected in thecontext's ancestors. In other words, encapsulation!

Why is encapsulation important?

Well, let's say you are creating a new disruptive startup, what do you do? Youcreate an API server with all your stuff, everything in the same place, amonolith!

Ok, you are growing very fast and you want to change your architecture and trymicroservices. Usually, this implies a huge amount of work, because of crossdependencies and a lack of separation of concerns in the codebase.

Fastify helps you in that regard. Thanks to the encapsulation model, it willcompletely avoid cross dependencies and will help you structure your code intocohesive blocks.

Let's return to how to correctly use register.

As you probably know, the required plugins must expose a single function withthe following signature

module.exports = function (fastify, options, done) {}

Where fastify is the encapsulated Fastify instance, options is the optionsobject, and done is the function you must call when your plugin is ready.

Fastify's plugin model is fully reentrant and graph-based, it handlesasynchronous code without any problems and it enforces both the load and closeorder of plugins. How? Glad you asked, check outavvio! Fastify starts loading the pluginafter .listen(), .inject() or .ready() are called.

Inside a plugin you can do whatever you want, register routes, utilities (wewill see this in a moment) and do nested registers, just remember to call donewhen everything is set up!

module.exports = function (fastify, options, done) {
fastify.get('/plugin', (request, reply) => {
reply.send({ hello: 'world' })
})

done()
}

Well, now you know how to use the register API and how it works, but how do weadd new functionality to Fastify and even better, share them with otherdevelopers?

Decorators

Okay, let's say that you wrote a utility that is so good that you decided tomake it available along with all your code. How would you do it? Probablysomething like the following:

// your-awesome-utility.js
module.exports = function (a, b) {
return a + b
}
const util = require('./your-awesome-utility')
console.log(util('that is ', 'awesome'))

Now you will import your utility in every file you need it in. (And do notforget that you will probably also need it in your tests).

Fastify offers you a more elegant and comfortable way to do this, decorators.Creating a decorator is extremely easy, just use thedecorate API:

fastify.decorate('util', (a, b) => a + b)

Now you can access your utility just by calling fastify.util whenever you needit - even inside your test.

And here starts the magic; do you remember how just now we were talking aboutencapsulation? Well, using register and decorate in conjunction enableexactly that, let me show you an example to clarify this:

fastify.register((instance, opts, done) => {
instance.decorate('util', (a, b) => a + b)
console.log(instance.util('that is ', 'awesome'))

done()
})

fastify.register((instance, opts, done) => {
console.log(instance.util('that is ', 'awesome')) // This will throw an error

done()
})

Inside the second register call instance.util will throw an error becauseutil exists only inside the first register context.

Let's step back for a moment and dig deeper into this: every time you use theregister API, a new context is created which avoids the negative situationsmentioned above.

Do note that encapsulation applies to the ancestors and siblings, but not thechildren.

fastify.register((instance, opts, done) => {
instance.decorate('util', (a, b) => a + b)
console.log(instance.util('that is ', 'awesome'))

fastify.register((instance, opts, done) => {
console.log(instance.util('that is ', 'awesome')) // This will not throw an error
done()
})

done()
})

fastify.register((instance, opts, done) => {
console.log(instance.util('that is ', 'awesome')) // This will throw an error

done()
})

Take home message: if you need a utility that is available in every part ofyour application, take care that it is declared in the root scope of yourapplication. If that is not an option, you can use the fastify-plugin utilityas described here.

decorate is not the only API that you can use to extend the serverfunctionality, you can also use decorateRequest and decorateReply.

decorateRequest and decorateReply? Why do we need them if we already havedecorate?

Good question, we added them to make Fastify more developer-friendly. Let's seean example:

fastify.decorate('html', payload => {
return generateHtml(payload)
})

fastify.get('/html', (request, reply) => {
reply
.type('text/html')
.send(fastify.html({ hello: 'world' }))
})

It works, but it could be much better!

fastify.decorateReply('html', function (payload) {
this.type('text/html') // This is the 'Reply' object
this.send(generateHtml(payload))
})

fastify.get('/html', (request, reply) => {
reply.html({ hello: 'world' })
})

Reminder that the this keyword is not available on arrow functions,so when passing functions in decorateReply and decorateRequest asa utility that also needs access to the request and reply instance,a function that is defined using the function keyword is needed insteadof an arrow function expression.

In the same way you can do this for the request object:

fastify.decorate('getHeader', (req, header) => {
return req.headers[header]
})

fastify.addHook('preHandler', (request, reply, done) => {
request.isHappy = fastify.getHeader(request.raw, 'happy')
done()
})

fastify.get('/happiness', (request, reply) => {
reply.send({ happy: request.isHappy })
})

Again, it works, but it can be much better!

fastify.decorateRequest('setHeader', function (header) {
this.isHappy = this.headers[header]
})

fastify.decorateRequest('isHappy', false) // This will be added to the Request object prototype, yay speed!

fastify.addHook('preHandler', (request, reply, done) => {
request.setHeader('happy')
done()
})

fastify.get('/happiness', (request, reply) => {
reply.send({ happy: request.isHappy })
})

We have seen how to extend server functionality and how to handle theencapsulation system, but what if you need to add a function that must beexecuted whenever the server "emits" anevent?

Hooks

You just built an amazing utility, but now you need to execute that for everyrequest, this is what you will likely do:

fastify.decorate('util', (request, key, value) => { request[key] = value })

fastify.get('/plugin1', (request, reply) => {
fastify.util(request, 'timestamp', new Date())
reply.send(request)
})

fastify.get('/plugin2', (request, reply) => {
fastify.util(request, 'timestamp', new Date())
reply.send(request)
})

I think we all agree that this is terrible. Repeated code, awful readability andit cannot scale.

So what can you do to avoid this annoying issue? Yes, you are right, use ahook!

fastify.decorate('util', (request, key, value) => { request[key] = value })

fastify.addHook('preHandler', (request, reply, done) => {
fastify.util(request, 'timestamp', new Date())
done()
})

fastify.get('/plugin1', (request, reply) => {
reply.send(request)
})

fastify.get('/plugin2', (request, reply) => {
reply.send(request)
})

Now for every request, you will run your utility. You can register as many hooksas you need.

Sometimes you want a hook that should be executed for just a subset of routes,how can you do that? Yep, encapsulation!

fastify.register((instance, opts, done) => {
instance.decorate('util', (request, key, value) => { request[key] = value })

instance.addHook('preHandler', (request, reply, done) => {
instance.util(request, 'timestamp', new Date())
done()
})

instance.get('/plugin1', (request, reply) => {
reply.send(request)
})

done()
})

fastify.get('/plugin2', (request, reply) => {
reply.send(request)
})

Now your hook will run just for the first route!

An alternative approach is to make use of the onRoute hookto customize application routes dynamically from inside the plugin. Every timea new route is registered, you can read and modify the route options. For example,based on a route config option:

fastify.register((instance, opts, done) => {
instance.decorate('util', (request, key, value) => { request[key] = value })

function handler(request, reply, done) {
instance.util(request, 'timestamp', new Date())
done()
}

instance.addHook('onRoute', (routeOptions) => {
if (routeOptions.config && routeOptions.config.useUtil === true) {
// set or add our handler to the route preHandler hook
if (!routeOptions.preHandler) {
routeOptions.preHandler = [handler]
return
}
if (Array.isArray(routeOptions.preHandler)) {
routeOptions.preHandler.push(handler)
return
}
routeOptions.preHandler = [routeOptions.preHandler, handler]
}
})

fastify.get('/plugin1', {config: {useUtil: true}}, (request, reply) => {
reply.send(request)
})

fastify.get('/plugin2', (request, reply) => {
reply.send(request)
})

done()
})

This variant becomes extremely useful if you plan to distribute your plugin, asdescribed in the next section.

As you probably noticed by now, request and reply are not the standardNode.js request and response objects, but Fastify's objects.

How to handle encapsulation and distribution

Perfect, now you know (almost) all of the tools that you can use to extendFastify. Nevertheless, chances are that you came across one big issue: how isdistribution handled?

The preferred way to distribute a utility is to wrap all your code inside aregister. Using this, your plugin can support asynchronous bootstrapping(since decorate is a synchronous API), in the case of a database connectionfor example.

Wait, what? Didn't you tell me that register creates an encapsulation andthat the stuff I create inside will not be available outside?

Yes, I said that. However, what I didn't tell you is that you can tell Fastifyto avoid this behavior with thefastify-plugin module.

const fp = require('fastify-plugin')
const dbClient = require('db-client')

function dbPlugin (fastify, opts, done) {
dbClient.connect(opts.url, (err, conn) => {
fastify.decorate('db', conn)
done()
})
}

module.exports = fp(dbPlugin)

You can also tell fastify-plugin to check the installed version of Fastify, incase you need a specific API.

As we mentioned earlier, Fastify starts loading its plugins after.listen(), .inject() or .ready() are called and as such, after theyhave been declared. This means that, even though the plugin may inject variablesto the external Fastify instance via decorate,the decorated variables will not be accessible before calling .listen(),.inject() or .ready().

In case you rely on a variable injected by a preceding plugin and want to passthat in the options argument of register, you can do so by using a functioninstead of an object:

const fastify = require('fastify')()
const fp = require('fastify-plugin')
const dbClient = require('db-client')

function dbPlugin (fastify, opts, done) {
dbClient.connect(opts.url, (err, conn) => {
fastify.decorate('db', conn)
done()
})
}

fastify.register(fp(dbPlugin), { url: 'https://example.com' })
fastify.register(require('your-plugin'), parent => {
return { connection: parent.db, otherOption: 'foo-bar' }
})

In the above example, the parent variable of the function passed in as thesecond argument of register is a copy of the external Fastify instancethat the plugin was registered at. This means that we can access anyvariables that were injected by preceding plugins in the order of declaration.

ESM support

ESM is supported as well from Node.jsv13.3.0 and above! Just export your pluginas an ESM module and you are good to go!

// plugin.mjs
async function plugin (fastify, opts) {
fastify.get('/', async (req, reply) => {
return { hello: 'world' }
})
}

export default plugin

Handle errors

One of your plugins may fail during startup. Maybe you expect itand you have a custom logic that will be triggered in that case. How can youimplement this? The after API is what you need. after simply registers acallback that will be executed just after a register, and it can take up tothree parameters.

The callback changes based on the parameters you are giving:

  1. If no parameter is given to the callback and there is an error, that errorwill be passed to the next error handler.
  2. If one parameter is given to the callback, that parameter will be the errorobject.
  3. If two parameters are given to the callback, the first will be the errorobject; the second will be the done callback.
  4. If three parameters are given to the callback, the first will be the errorobject, the second will be the top-level context unless you have specifiedboth server and override, in that case, the context will be what the overridereturns, and the third the done callback.

Let's see how to use it:

fastify
.register(require('./database-connector'))
.after(err => {
if (err) throw err
})

Custom errors

If your plugin needs to expose custom errors, you can easily generate consistenterror objects across your codebase and plugins with the@fastify/error module.

const createError = require('@fastify/error')
const CustomError = createError('ERROR_CODE', 'message')
console.log(new CustomError())

Emit Warnings

If you want to deprecate an API, or you want to warn the user about a specificuse case, you can use theprocess-warning module.

const warning = require('process-warning')()
warning.create('MyPluginWarning', 'MP_ERROR_CODE', 'message')
warning.emit('MP_ERROR_CODE')

Let's start!

Awesome, now you know everything you need to know about Fastify and its pluginsystem to start building your first plugin, and please if you do, tell us! Wewill add it to the ecosystemsection of our documentation!

If you want to see some real-world examples, check out:

  • @fastify/view Templatesrendering (ejs, pug, handlebars, marko) plugin support for Fastify.
  • @fastify/mongodb FastifyMongoDB connection plugin, with this you can share the same MongoDB connectionpool in every part of your server.
  • @fastify/multipart Multipartsupport for Fastify
  • @fastify/helmet Importantsecurity headers for Fastify

Do you feel like something is missing here? Let us know! :)

The hitchhiker's guide to plugins | Fastify (2024)

FAQs

Why use Fastify plugin? ›

A rich Fastify plugin architecture with encapsulation

Fastify has a rich plugin architecture that supports encapsulation. This means you can easily break down your application into isolated components, each with its own set of routes, plugins, and decorators.

How to write fastify plugin? ›

Creating a plugin is very easy, you just need to create a function that takes three parameters, the fastify instance, an options object, and the done callback. Sometimes, you will need to know when the server is about to close, for example, because you must close a connection to a database.

What does Fastify Register do? ›

register creates a new Fastify context, which means that if you perform any changes on the Fastify instance, those changes will not be reflected in the context's ancestors. In other words, encapsulation!

Is Fastify better than Express? ›

Fastify is a great option for creating effective web applications and APIs because of its lightweight design and emphasis on performance. If you value ease of use, adaptability, and a well-developed ecosystem, go with Express.

Why is fastify so fast? ›

Fastify provides full encapsulation for plug-ins, automatically parses JSON with relatively faster rendering, and provides quick routing. Among other benefits, Fastify also has a cleaner syntax for writing async code in controllers. Fastify is consistently faster than Express by 2–3 seconds.

Is Fastify open source? ›

It's important to mention that Fastify is a project under the OpenJS Foundation, ensuring its commitment to remaining open-source. By contributing to Fastify, you are not only supporting a powerful web framework but also investing in the principles of open-source development and community collaboration.

What is a decorator in Fastify? ›

Decorators​ The decorators API allows customization of the core Fastify objects, such as the server instance itself and any request and reply objects used during the HTTP request lifecycle. The decorators API can be used to attach any type of property to the core objects, e.g. functions, plain objects, or native types.

What is Fastify used for? ›

Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town.

What is fastify used for? ›

Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town.

Should I use NestJS or fastify? ›

Choosing between Fastify and NestJS depends largely on the specific needs of your project. Fastify excels in performance and simplicity, making it ideal for high-performance applications and those requiring fine-grained control over the architecture.

Why do we use Maven Surefire plugin? ›

Maven Surefire is a plugin that executes the unit tests of a Maven application or project. It comes to play in the testing phase of the Maven build lifecycle. After testing, this plugin also generates the unit test report. It can generate the report in two formats, plain text, and XML.

Why do we use Maven failsafe plugin? ›

The Failsafe Plugin is used during the integration-test and verify phases of the build lifecycle to execute the integration tests of an application. The Failsafe Plugin will not fail the build during the integration-test phase, thus enabling the post-integration-test phase to execute.

Top Articles
10+ Chia Seed Substitutes for EVERY Occasion
BST Comparison - Tyrel Clayton - BST Basics
Fat Hog Prices Today
Truist Park Section 135
How to know if a financial advisor is good?
Stolen Touches Neva Altaj Read Online Free
AB Solutions Portal | Login
Tabler Oklahoma
Milk And Mocha GIFs | GIFDB.com
Uc Santa Cruz Events
Goldsboro Daily News Obituaries
Rosemary Beach, Panama City Beach, FL Real Estate & Homes for Sale | realtor.com®
FAQ: Pressure-Treated Wood
Cooking Fever Wiki
Check From Po Box 1111 Charlotte Nc 28201
Tvtv.us Duluth Mn
Swgoh Turn Meter Reduction Teams
Nesz_R Tanjiro
Nordstrom Rack Glendale Photos
Nurse Logic 2.0 Testing And Remediation Advanced Test
라이키 유출
Quest: Broken Home | Sal's Realm of RuneScape
Barber Gym Quantico Hours
Titanic Soap2Day
Red Cedar Farms Goldendoodle
Great ATV Riding Tips for Beginners
Umn Biology
Mobile crane from the Netherlands, used mobile crane for sale from the Netherlands
Log in or sign up to view
Mercedes W204 Belt Diagram
Loopnet Properties For Sale
Craigslist Hamilton Al
Dynavax Technologies Corp (DVAX)
How To Get Soul Reaper Knife In Critical Legends
Ashoke K Maitra. Adviser to CMD's. Received Lifetime Achievement Award in HRD on LinkedIn: #hr #hrd #coaching #mentoring #career #jobs #mba #mbafreshers #sales…
M Life Insider
Stewartville Star Obituaries
Karen Wilson Facebook
Best GoMovies Alternatives
Satucket Lectionary
Grizzly Expiration Date Chart 2023
All Weapon Perks and Status Effects - Conan Exiles | Game...
M&T Bank
New Starfield Deep-Dive Reveals How Shattered Space DLC Will Finally Fix The Game's Biggest Combat Flaw
Enjoy Piggie Pie Crossword Clue
Billings City Landfill Hours
Ics 400 Test Answers 2022
Frank 26 Forum
Service Changes and Self-Service Options
Bumgarner Funeral Home Troy Nc Obituaries
Cataz.net Android Movies Apk
Primary Care in Nashville & Southern KY | Tristar Medical Group
Latest Posts
Article information

Author: Jeremiah Abshire

Last Updated:

Views: 6091

Rating: 4.3 / 5 (74 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Jeremiah Abshire

Birthday: 1993-09-14

Address: Apt. 425 92748 Jannie Centers, Port Nikitaville, VT 82110

Phone: +8096210939894

Job: Lead Healthcare Manager

Hobby: Watching movies, Watching movies, Knapping, LARPing, Coffee roasting, Lacemaking, Gaming

Introduction: My name is Jeremiah Abshire, I am a outstanding, kind, clever, hilarious, curious, hilarious, outstanding person who loves writing and wants to share my knowledge and understanding with you.