5

In Fastify.js you have at least to ways to register hooks: globally (via fastify.addHook()) or as a property inside the route declaration. In the example below I'm trying to use fastfy-multer to handle file uploading but the maximum amount of files must be limited by a setting associated with a "room". As the app has many rooms, most of the requests contain a reference to a room, and every time the request is being augmented with room settings by the preHandler hook.

import fastify from 'fastify'
import multer from 'fastify-multer'

const server = fastify()
server.register(multer.contentParser)

// For all requests containing the room ID, fetch the room options from the database
fastify.addHook('preHandler', async (request, reply) => {
    if (request.body.roomID) {
        const roomOptions = await getRoomOptions(request.body.roomID)
        if (roomOptions) {
            reuqest.body.room = roomOptions
        }
        else {
            // handle an error if the room doesn't exist
        }
    }
})

server.post('/post', {
    // Limit the maximum amount of files to be uploaded based on room options
    preHandler: upload.array(files, request.body.room.maxFiles)
})

In order for this setup to work, the global hook must always be executed before the file upload hook. How can I guarantee that?

Juribiyan
  • 700
  • 8
  • 25

1 Answers1

6

Summary: As @Manuel Spigolon said:

How can I guarantee that? The framework does it

Now we can take Manuel's word for it (SPOILER ALERT: they are absolutely correct), or we can prove how this works by looking in the source code on GitHub.

The first thing to keep in mind is that arrays in JavaScript are remain ordered by the way objects are pushed into them, but don’t take my word for it. That is all explained here if you want to dive a little deeper into the evidence. If that was not true, everything below doesn't matter and you could just stop reading now.

How addHook works

Now that we have established that arrays maintain their order, let look at how the addHook code is executed. We can start by looking at the default export of fastify in the fastify.js file located in the root directory. In this object if scoll down you'll see the addHook property defined. When we look into the addHook function implementation we can see that in that add hook call we are calling this[kHooks].add.

When we go back to see what the kHooks property is we see that it is a new Hooks(). When we go to take a look at the add method on the Hooks object, we can see that it just validates the hook that is being add and then [pushes] it to the array property on the Hooks object with the matching hook name. This shows that hooks will always be in the order which add was called for them.

How fastify.route adds hooks

I hope you're following to this point because that only proves the order of the addHook calls in the respective array on the Hooks object. The next question is how these interact with the calls of fastify.(get | post | route | ...) functions. We can walk through the fastify.get function, but they are all pretty much the same (you can do the same exercise with any of them). Looking at the get function, we see that the implementation is just calling the router.prepareRoute function. When you look into the prepareRoute implementation, you see that this function returns a call to the route function. In the route function there is a section where the hooks are set up. It looks like this:

for (const hook of lifecycleHooks) {
  const toSet = this[kHooks][hook]
    .concat(opts[hook] || [])
    .map(h => h.bind(this))
  context[hook] = toSet.length ? toSet : null
}

What this does is go through every lifecycle hook and turn it into a set of all the hooks from the Fastify instance (this) and the hooks in the options (opts[hook]) for that given hook and binds them to the fastify instance (this). This shows that the hooks in the options for the routes are always added after the addHook handlers.

How Fastify executes hooks

This is not everything we need though. Now we know the order in which the hooks are stored. But how exactly are they executed? For that we can look at the hookRunner function in the hooks.js file. We see this function acts as a sort of recursive loop that continues running as long as the handlers do not error. It first creates a variable i to keep track of the handler function it is currently on and then tries to execute it and increments the function tracker (i).

If the handler fails (handleReject), it runs a callback function and does not call the next function to continue. If the handler succeeds (handleResolve), it just runs the next function to try the same process on the following handler (functions[i++]) in the functions set.

Why does this matter

This proves that the hook handlers are called in the order that they were pushed into the ordered collection. In other words:

How can I guarantee that? The framework does it

halfer
  • 19,824
  • 17
  • 99
  • 186
tim117
  • 244
  • 2
  • 14