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
101089742 Routing Number PATHWARD, NATIONAL ASSOCIATION SD - Wise
Update to add support for TLS 1.1 and TLS 1.2 in Windows Embedded Compact 7
Hometown Pizza Sheridan Menu
Toa Guide Osrs
Research Tome Neltharus
Ixl Elmoreco.com
Chase Bank Operating Hours
Craigslist Kennewick Pasco Richland
Teamexpress Login
Remnant Graveyard Elf
Craigslist Greenville Craigslist
R/Altfeet
Nonuclub
Teenleaks Discord
Bfg Straap Dead Photo Graphic
Echat Fr Review Pc Retailer In Qatar Prestige Pc Providers – Alpha Marine Group
NBA 2k23 MyTEAM guide: Every Trophy Case Agenda for all 30 teams
Msu 247 Football
Halo Worth Animal Jam
Timeforce Choctaw
Www.craigslist.com Savannah Ga
Gazette Obituary Colorado Springs
Talk To Me Showtimes Near Marcus Valley Grand Cinema
Project Reeducation Gamcore
Haunted Mansion Showtimes Near Epic Theatres Of West Volusia
48 Oz Equals How Many Quarts
Craigslist Panama City Beach Fl Pets
Tinyzonehd
Keshi with Mac Ayres and Starfall (Rescheduled from 11/1/2024) (POSTPONED) Tickets Thu, Nov 1, 2029 8:00 pm at Pechanga Arena - San Diego in San Diego, CA
Things to do in Pearl City: Honolulu, HI Travel Guide by 10Best
Winterset Rants And Raves
Golden Tickets
Unity Webgl Player Drift Hunters
Vanessa West Tripod Jeffrey Dahmer
Quake Awakening Fragments
Crazy Balls 3D Racing . Online Games . BrightestGames.com
Legit Ticket Sites - Seatgeek vs Stubhub [Fees, Customer Service, Security]
Sabrina Scharf Net Worth
Fetus Munchers 1 & 2
Panorama Charter Portal
2007 Jaguar XK Low Miles for sale - Palm Desert, CA - craigslist
Atom Tickets – Buy Movie Tickets, Invite Friends, Skip Lines
Acts 16 Nkjv
Sofia With An F Mugshot
Child care centers take steps to avoid COVID-19 shutdowns; some require masks for kids
Dragon Ball Super Card Game Announces Next Set: Realm Of The Gods
The Quiet Girl Showtimes Near Landmark Plaza Frontenac
Germany’s intensely private and immensely wealthy Reimann family
Craigslist Anc Ak
211475039
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.