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.