Fastify Fundamentals: Quick-starting Hooks And Decorations

Fastify Fundamentals: Quick-starting Hooks And Decorations

Great code should be clean, understandable, and easily modifiable. This is why every Node.js developer should learn how to add hooks and decorators to their code.

Decorators enable you to enhance the functionalities of your routes. Hooks, on the other hand, enable developers to modify various parts of their applications as may be needed, and better handle event listening.

This tutorial will teach you how you can implement hooks and decorations in real time with Fastify.

An Introduction to Fastify’s Functionalities

Fastify is built around a huge system of extensibility. The key functionalities that make Fastify extensible, simple and fast are:

1. Plugins and Encapsulation

Fastify plugins help smoothly extend Fastify's functionality. Plugins can be various elements such as specific routes, or utilities, in the form of helper functions or objects for an application. Fastify’s approach to plugins is underpinned by one of its technical principles: If something could be a plugin, it likely should be.

Plugins operate within their scope thereby preventing conflicts within the application.

Plugins can be created for numerous purposes, from database connection to security headers. In addition, they allow inheritance. This means a child plugin can inherit functionality from the parent plugin.

As a check, Fastify has a concept of encapsulation, which ensures whatever is defined in a plugin stays only within it and does not interfere outside of it. In other words, decorators ensure that every route has its hidden class of handling Req and Res.

2. Decorators

In Fastify, decorators are a feature that allows developers to add information, metadata or behaviour to code elements such as classes, methods and properties. Essentially, they enhance the code without directly modifying Fastify’s core functionalities.

There are two approaches to using decorators: the built-in Fastify decorators and third-party decorators.

The built-in Fastify decorators operate more with core objects such as Request, Reply and Server.

You can find out more about decorators in this video:

3. Hooks

Hooks are special functions that allow developers to modify the behaviours of their applications at various points in the request/response lifecycle. They also let the application listen to specific events.

There are two types of hooks: Request/Reply hooks and Application hooks.

The Request/Reply hooks enable developers to manipulate the request/response data, perform authentication checks, and handle errors gracefully. The application hooks apply to the application execution on a global level.

Common use cases for hooks include authentication and authorization, error handling, data preprocessing and post-processing before sending it back to the user.

Request Lifecycle and Hooks

Fastify is based on a lifecycle/hook model where hooks are per-route (or per-encapsulation context), guaranteeing that only what is needed is executed.

When a request is received, routing occurs immediately. Following this, a logger is instantiated and subsequently passes through the steps shown below.

This is contrary to the middleware-based system whereby the route passes through all the routes specified. In Fastify, there is a limited number of steps– as such, you can’t insert code in each of these steps.

This implies that there is nothing that can be inserted in the middle. When a function is called in the middle, it will be treated either as a pre or post-function.

Setting Up Hooks

To use a hook, we can specify an “onRequest” hook as shown below.

fastify.addHook('onRequest', async(request, reply ) => {
  //some code
  await asyncMethod()
})

We can specify a code that logs some information on a route as shown below. It can be specified for one specific route.

async function logMeHook (request, reply) {
    request.log.info({ route: request.routerPath }, 'logMe hook')
  }


app.get('/logMe', {
  onRequest: logMeHook
}, async (request, reply) => {
  return { hello: 'world' }
})

Let’s modify our prototype to log another line whenever a request is received. To do this, we can simply use the addHook method as shown below and add to our code.

app.addHook('onRequest', async(request, reply)=> {
  request.log.info({ url: request.url}, 'incoming request2')
})

When a request is sent to any of the routes, an extra line with the info “incoming request2” is logged in the terminal as shown below:

onRoute Hook

The onRoute hook within Fastify makes it possible to metaprogram routes. With this hook, you can alter routes to change their behaviour in a central place.

This procedure is often referred to as “aspect-oriented programming”. Multiple onRoute hooks can be applied simultaneously - making it possible to add different behavior.

The hook can be used to add logs to some selected routes as shown below. The routeOptions.onRequest may also be an array so it is important to check through them.

  app.addHook('onRoute', function (routeOptions) {
    if (routeOptions.config?.logMe) {
      if (!Array.isArray(routeOptions.onRequest)) {
        if (routeOptions.onRequest) {
          routeOptions.onRequest = [routeOptions.onRequest]
        } else {
          routeOptions.onRequest = []
        }
      }
      routeOptions.onRequest.push(logMeHook)
    }
  })

  // should log
  app.get('/', { config: { logMe: true } }, async (request, reply) => {
    return { hello: 'world' }
  })

  // should not log
  app.get('/error', async (request, reply) => {
    throw new Error('kaboom')
  })

Decorators

Decorators allow developers to attach information to core objects, like adding the user to an incoming request. Decorators allow for the attachment of properties to the request, without incurring the huge cost of optimizations and megamorphisms.

In essence, decorators ensure every route has its own hidden class for Request and Reply, allowing V8 to optimize your code to the fullest extent.

//Decorate request with a user property
    app.decorate('user', '')

    //
    // app.decorateRequest()
    fastify.addHook('preHandler', (req, reply, done)=> {
      req.user= 'Bob Dylan'
      done()
    })

    //And Finally access it
    // app.decorateReply()
    fastify.get('/', (req, reply)=> {
      reply.send(`Hello, ${req.user}`)
    })

The fastify-user module helps handle this functionality, offering to extract user and support for both JWT and Webhooks.

Wrapping Up

In this article, we looked at the built-in Fastify decorators, and how to use them in your applications. We also explored how to use onRequest and onRoute hooks.

Supercharging Fastify Development with Platformatic

Developed by the co-creator of Fastify, Platformatic is a backend development platform designed to extend the capabilities of the Fastify web framework. Together, Platformatic and Fastify offer:

  • Developer-centric design

  • Real-time metrics

  • A vast plugin ecosystem

  • Built-in validation and serialization

  • Built-in logging

Find out more and get in touch.