DEV Community

loading...

Fastify plugin system

Amis
・3 min read

Why a plugin system?

Let's say I need to send queries to the database from different modules of my app. Should I connect to db, export the connection, and then import it in all modules where I need to use it? Well, it works but it results in a spaghetti code. This is where Fastify plugin system comes in handy. It let's you to plug ( inject ) your dependencies to Fastify instance and then you can use them wherever you have access to the instance. It also helps you to move from a monolithic structure to microservices easily, because every service can be a plugin itself.

How does a plugin looks like?

A plugin is just a function that takes in fastify and options as inputs.

This is how a plugin looks like:

function pluginA(fastify, options, done) {
    // ...
    done()
}
Enter fullscreen mode Exit fullscreen mode

and this is how an async plugin looks like:

async function pluginB(fastify, options) {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

and here is how to register them:

import Fastify from 'fastify'

const fastify = Fastify()

fastify
    .register(pluginA)
    .register(pluginB)
Enter fullscreen mode Exit fullscreen mode

Encapsulation

What encapsulation means is that a copy of Fastify instance is passed to the plugin when you register it. So anything you add to ( decorate ) Fastify instance would only be accessible inside the plugin.

Fastify plugins with encapsulation

import Fastify from 'fastify'

const fastify = Fastify()

fastify
    .register(function pluginA(fastify, options, done) {
        // Add a random number to fastify instance
        fastify.decorate('rand', Math.random())

        console.log(fastify.rand) // Accessible here

        done()
    })
    .register(function pluginB(fastify, options, done) {
        // Try to access the random number added in pluginA
        console.log(fastify.rand) // undefined

        done()
    })
Enter fullscreen mode Exit fullscreen mode

What if you don't want encapsulation? You can use fastify-plugin package to register a plugin to the main Fastify instance.

Fastify plugins without encapsulation

import Fastify from 'fastify'
import fp from 'fastify-plugin'

const fastify = Fastify()

fastify
    // Register pluginA with fastify-plugin
    .register(fp(function pluginA(fastify, options, done) {
        // Add a random number to fastify instance
        fastify.decorate('rand', Math.random())

        console.log(fastify.rand) // Accessible here

        done()
    }))
    .register(function pluginB(fastify, options, done) {
        // Try to access the random number added in pluginA
        console.log(fastify.rand) // Also accessible here

        done()
    })
Enter fullscreen mode Exit fullscreen mode

Packages like fastify-postgres, fastify-mongodb, fastify-redis, ... they all use fastify-plugin so you won't have to register them with fastify-plugin.

Here is the simplified version of fastify-postgres plugin:

const pg = require('pg')
const fp = require('fastify-plugin')

function fastifyPostgres(fastify, options, next) {
    const pool = new pg.Pool(options)
    const db = {
        connect: pool.connect.bind(pool),
        pool: pool,
        Client: pg.Client,
        query: pool.query.bind(pool),
        transact: transact.bind(pool)
    }
    // Inject postgres connection to Fastify instance
    fastify.decorate('pg', db)
    next()
}

module.exports = fp(fastifyPostgres)
Enter fullscreen mode Exit fullscreen mode

How to access registered plugins from route handlers?

Here I used PostgresSQL as the database of my app. I want to be able to use it inside my route handlers to send queries to the database:

// index.js

import Fastify from 'fastify'
import pg from 'fastify-postgres'
import routes from './routes.js'

const fastify = Fastify()

fastify
    .register(pg, {
        connectionString: 'postgres://postgres@localhost/postgres'
    })
    .register(routes)
Enter fullscreen mode Exit fullscreen mode

Here I defined route handlers inside a plugin, so I can access fastify.pg there:

// routes.js

export default function routes(fastify, options, done) {
    fastify.route({
        method: 'GET',
        url: '/',
        handler: (req, reply) => {
            // Have access to fastify.pg here
        }
    })

    done()
}
Enter fullscreen mode Exit fullscreen mode

Here I defined route handlers in a seperate module, so in order to access fastify I need to use the this keyword.

NOTE: Normal functions must be used rather than arrow functions to define route handlers. Read more here.

// routes.js

import { mainHandler } from './handlers.js'

export default function routes(fastify, options, done) {
    fastify.route({
        method: 'GET',
        url: '/',
        handler: mainHandler
    })

    done()
}
Enter fullscreen mode Exit fullscreen mode
// handlers.js

export function mainHandler(req, reply) {
    // Have access to this.pg here
}
Enter fullscreen mode Exit fullscreen mode

End

I recommend you to read the docs if you still have questions about Fastify plugins. You can also join Fastify discord channel, there is a fantastic community there that can help you out.

Discussion (1)

Collapse
odili profile image
Chikaodili Chukwunta

Very helpful, Thanks