Spaces:
Runtime error
Runtime error
| <h1 align="center">Fastify</h1> | |
| ## Hooks | |
| Hooks are registered with the `fastify.addHook` method and allow you to listen | |
| to specific events in the application or request/response lifecycle. You have to | |
| register a hook before the event is triggered, otherwise, the event is lost. | |
| By using hooks you can interact directly with the lifecycle of Fastify. There | |
| are Request/Reply hooks and application hooks: | |
| - [Request/Reply Hooks](#requestreply-hooks) | |
| - [onRequest](#onrequest) | |
| - [preParsing](#preparsing) | |
| - [preValidation](#prevalidation) | |
| - [preHandler](#prehandler) | |
| - [preSerialization](#preserialization) | |
| - [onError](#onerror) | |
| - [onSend](#onsend) | |
| - [onResponse](#onresponse) | |
| - [onTimeout](#ontimeout) | |
| - [onRequestAbort](#onrequestabort) | |
| - [Manage Errors from a hook](#manage-errors-from-a-hook) | |
| - [Respond to a request from a hook](#respond-to-a-request-from-a-hook) | |
| - [Application Hooks](#application-hooks) | |
| - [onReady](#onready) | |
| - [onListen](#onlisten) | |
| - [onClose](#onclose) | |
| - [preClose](#preclose) | |
| - [onRoute](#onroute) | |
| - [onRegister](#onregister) | |
| - [Scope](#scope) | |
| - [Route level hooks](#route-level-hooks) | |
| - [Using Hooks to Inject Custom Properties](#using-hooks-to-inject-custom-properties) | |
| - [Diagnostics Channel Hooks](#diagnostics-channel-hooks) | |
| **Notice:** the `done` callback is not available when using `async`/`await` or | |
| returning a `Promise`. If you do invoke a `done` callback in this situation | |
| unexpected behavior may occur, e.g. duplicate invocation of handlers. | |
| ## Request/Reply Hooks | |
| [Request](./Request.md) and [Reply](./Reply.md) are the core Fastify objects. | |
| `done` is the function to continue with the [lifecycle](./Lifecycle.md). | |
| It is easy to understand where each hook is executed by looking at the | |
| [lifecycle page](./Lifecycle.md). | |
| Hooks are affected by Fastify's encapsulation, and can thus be applied to | |
| selected routes. See the [Scopes](#scope) section for more information. | |
| There are eight different hooks that you can use in Request/Reply *(in order of | |
| execution)*: | |
| ### onRequest | |
| ```js | |
| fastify.addHook('onRequest', (request, reply, done) => { | |
| // Some code | |
| done() | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('onRequest', async (request, reply) => { | |
| // Some code | |
| await asyncMethod() | |
| }) | |
| ``` | |
| **Notice:** in the [onRequest](#onrequest) hook, `request.body` will always be | |
| `undefined`, because the body parsing happens before the | |
| [preValidation](#prevalidation) hook. | |
| ### preParsing | |
| If you are using the `preParsing` hook, you can transform the request payload | |
| stream before it is parsed. It receives the request and reply objects as other | |
| hooks, and a stream with the current request payload. | |
| If it returns a value (via `return` or via the callback function), it must | |
| return a stream. | |
| For instance, you can decompress the request body: | |
| ```js | |
| fastify.addHook('preParsing', (request, reply, payload, done) => { | |
| // Some code | |
| done(null, newPayload) | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('preParsing', async (request, reply, payload) => { | |
| // Some code | |
| await asyncMethod() | |
| return newPayload | |
| }) | |
| ``` | |
| **Notice:** in the [preParsing](#preparsing) hook, `request.body` will always be | |
| `undefined`, because the body parsing happens before the | |
| [preValidation](#prevalidation) hook. | |
| **Notice:** you should also add a `receivedEncodedLength` property to the | |
| returned stream. This property is used to correctly match the request payload | |
| with the `Content-Length` header value. Ideally, this property should be updated | |
| on each received chunk. | |
| **Notice:** The size of the returned stream is checked to not exceed the limit | |
| set in [`bodyLimit`](./Server.md#bodylimit) option. | |
| ### preValidation | |
| If you are using the `preValidation` hook, you can change the payload before it | |
| is validated. For example: | |
| ```js | |
| fastify.addHook('preValidation', (request, reply, done) => { | |
| request.body = { ...request.body, importantKey: 'randomString' } | |
| done() | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('preValidation', async (request, reply) => { | |
| const importantKey = await generateRandomString() | |
| request.body = { ...request.body, importantKey } | |
| }) | |
| ``` | |
| ### preHandler | |
| The `preHandler` hook allows you to specify a function that is executed before | |
| a routes's handler. | |
| ```js | |
| fastify.addHook('preHandler', (request, reply, done) => { | |
| // some code | |
| done() | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('preHandler', async (request, reply) => { | |
| // Some code | |
| await asyncMethod() | |
| }) | |
| ``` | |
| ### preSerialization | |
| If you are using the `preSerialization` hook, you can change (or replace) the | |
| payload before it is serialized. For example: | |
| ```js | |
| fastify.addHook('preSerialization', (request, reply, payload, done) => { | |
| const err = null | |
| const newPayload = { wrapped: payload } | |
| done(err, newPayload) | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('preSerialization', async (request, reply, payload) => { | |
| return { wrapped: payload } | |
| }) | |
| ``` | |
| Note: the hook is NOT called if the payload is a `string`, a `Buffer`, a | |
| `stream`, or `null`. | |
| ### onError | |
| ```js | |
| fastify.addHook('onError', (request, reply, error, done) => { | |
| // Some code | |
| done() | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('onError', async (request, reply, error) => { | |
| // Useful for custom error logging | |
| // You should not use this hook to update the error | |
| }) | |
| ``` | |
| This hook is useful if you need to do some custom error logging or add some | |
| specific header in case of error. | |
| It is not intended for changing the error, and calling `reply.send` will throw | |
| an exception. | |
| This hook will be executed only after | |
| the [Custom Error Handler set by `setErrorHandler`](./Server.md#seterrorhandler) | |
| has been executed, and only if the custom error handler sends an error back to the | |
| user | |
| *(Note that the default error handler always sends the error back to the | |
| user)*. | |
| **Notice:** unlike the other hooks, passing an error to the `done` function is not | |
| supported. | |
| ### onSend | |
| If you are using the `onSend` hook, you can change the payload. For example: | |
| ```js | |
| fastify.addHook('onSend', (request, reply, payload, done) => { | |
| const err = null; | |
| const newPayload = payload.replace('some-text', 'some-new-text') | |
| done(err, newPayload) | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('onSend', async (request, reply, payload) => { | |
| const newPayload = payload.replace('some-text', 'some-new-text') | |
| return newPayload | |
| }) | |
| ``` | |
| You can also clear the payload to send a response with an empty body by | |
| replacing the payload with `null`: | |
| ```js | |
| fastify.addHook('onSend', (request, reply, payload, done) => { | |
| reply.code(304) | |
| const newPayload = null | |
| done(null, newPayload) | |
| }) | |
| ``` | |
| > You can also send an empty body by replacing the payload with the empty string | |
| > `''`, but be aware that this will cause the `Content-Length` header to be set | |
| > to `0`, whereas the `Content-Length` header will not be set if the payload is | |
| > `null`. | |
| Note: If you change the payload, you may only change it to a `string`, a | |
| `Buffer`, a `stream`, a `ReadableStream`, a `Response`, or `null`. | |
| ### onResponse | |
| ```js | |
| fastify.addHook('onResponse', (request, reply, done) => { | |
| // Some code | |
| done() | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('onResponse', async (request, reply) => { | |
| // Some code | |
| await asyncMethod() | |
| }) | |
| ``` | |
| The `onResponse` hook is executed when a response has been sent, so you will not | |
| be able to send more data to the client. It can however be useful for sending | |
| data to external services, for example, to gather statistics. | |
| **Note:** setting `disableRequestLogging` to `true` will disable any error log | |
| inside the `onResponse` hook. In this case use `try - catch` to log errors. | |
| ### onTimeout | |
| ```js | |
| fastify.addHook('onTimeout', (request, reply, done) => { | |
| // Some code | |
| done() | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('onTimeout', async (request, reply) => { | |
| // Some code | |
| await asyncMethod() | |
| }) | |
| ``` | |
| `onTimeout` is useful if you need to monitor the request timed out in your | |
| service (if the `connectionTimeout` property is set on the Fastify instance). | |
| The `onTimeout` hook is executed when a request is timed out and the HTTP socket | |
| has been hung up. Therefore, you will not be able to send data to the client. | |
| ### onRequestAbort | |
| ```js | |
| fastify.addHook('onRequestAbort', (request, done) => { | |
| // Some code | |
| done() | |
| }) | |
| ``` | |
| Or `async/await`: | |
| ```js | |
| fastify.addHook('onRequestAbort', async (request) => { | |
| // Some code | |
| await asyncMethod() | |
| }) | |
| ``` | |
| The `onRequestAbort` hook is executed when a client closes the connection before | |
| the entire request has been processed. Therefore, you will not be able to send | |
| data to the client. | |
| **Notice:** client abort detection is not completely reliable. See: [`Detecting-When-Clients-Abort.md`](../Guides/Detecting-When-Clients-Abort.md) | |
| ### Manage Errors from a hook | |
| If you get an error during the execution of your hook, just pass it to `done()` | |
| and Fastify will automatically close the request and send the appropriate error | |
| code to the user. | |
| ```js | |
| fastify.addHook('onRequest', (request, reply, done) => { | |
| done(new Error('Some error')) | |
| }) | |
| ``` | |
| If you want to pass a custom error code to the user, just use `reply.code()`: | |
| ```js | |
| fastify.addHook('preHandler', (request, reply, done) => { | |
| reply.code(400) | |
| done(new Error('Some error')) | |
| }) | |
| ``` | |
| *The error will be handled by [`Reply`](./Reply.md#errors).* | |
| Or if you're using `async/await` you can just throw an error: | |
| ```js | |
| fastify.addHook('onRequest', async (request, reply) => { | |
| throw new Error('Some error') | |
| }) | |
| ``` | |
| ### Respond to a request from a hook | |
| If needed, you can respond to a request before you reach the route handler, for | |
| example when implementing an authentication hook. Replying from a hook implies | |
| that the hook chain is __stopped__ and the rest of the hooks and handlers are | |
| not executed. If the hook is using the callback approach, i.e. it is not an | |
| `async` function or it returns a `Promise`, it is as simple as calling | |
| `reply.send()` and avoiding calling the callback. If the hook is `async`, | |
| `reply.send()` __must__ be called _before_ the function returns or the promise | |
| resolves, otherwise, the request will proceed. When `reply.send()` is called | |
| outside of the promise chain, it is important to `return reply` otherwise the | |
| request will be executed twice. | |
| It is important to __not mix callbacks and `async`/`Promise`__, otherwise the | |
| hook chain will be executed twice. | |
| If you are using `onRequest` or `preHandler` use `reply.send`. | |
| ```js | |
| fastify.addHook('onRequest', (request, reply, done) => { | |
| reply.send('Early response') | |
| }) | |
| // Works with async functions too | |
| fastify.addHook('preHandler', async (request, reply) => { | |
| setTimeout(() => { | |
| reply.send({ hello: 'from prehandler' }) | |
| }) | |
| return reply // mandatory, so the request is not executed further | |
| // Commenting the line above will allow the hooks to continue and fail with FST_ERR_REP_ALREADY_SENT | |
| }) | |
| ``` | |
| If you want to respond with a stream, you should avoid using an `async` function | |
| for the hook. If you must use an `async` function, your code will need to follow | |
| the pattern in | |
| [test/hooks-async.js](https://github.com/fastify/fastify/blob/94ea67ef2d8dce8a955d510cd9081aabd036fa85/test/hooks-async.js#L269-L275). | |
| ```js | |
| fastify.addHook('onRequest', (request, reply, done) => { | |
| const stream = fs.createReadStream('some-file', 'utf8') | |
| reply.send(stream) | |
| }) | |
| ``` | |
| If you are sending a response without `await` on it, make sure to always `return | |
| reply`: | |
| ```js | |
| fastify.addHook('preHandler', async (request, reply) => { | |
| setImmediate(() => { reply.send('hello') }) | |
| // This is needed to signal the handler to wait for a response | |
| // to be sent outside of the promise chain | |
| return reply | |
| }) | |
| fastify.addHook('preHandler', async (request, reply) => { | |
| // the @fastify/static plugin will send a file asynchronously, | |
| // so we should return reply | |
| reply.sendFile('myfile') | |
| return reply | |
| }) | |
| ``` | |
| ## Application Hooks | |
| You can hook into the application-lifecycle as well. | |
| - [onReady](#onready) | |
| - [onListen](#onlisten) | |
| - [onClose](#onclose) | |
| - [preClose](#preclose) | |
| - [onRoute](#onroute) | |
| - [onRegister](#onregister) | |
| ### onReady | |
| Triggered before the server starts listening for requests and when `.ready()` is | |
| invoked. It cannot change the routes or add new hooks. Registered hook functions | |
| are executed serially. Only after all `onReady` hook functions have completed | |
| will the server start listening for requests. Hook functions accept one | |
| argument: a callback, `done`, to be invoked after the hook function is complete. | |
| Hook functions are invoked with `this` bound to the associated Fastify instance. | |
| ```js | |
| // callback style | |
| fastify.addHook('onReady', function (done) { | |
| // Some code | |
| const err = null; | |
| done(err) | |
| }) | |
| // or async/await style | |
| fastify.addHook('onReady', async function () { | |
| // Some async code | |
| await loadCacheFromDatabase() | |
| }) | |
| ``` | |
| ### onListen | |
| Triggered when the server starts listening for requests. The hooks run one | |
| after another. If a hook function causes an error, it is logged and | |
| ignored, allowing the queue of hooks to continue. Hook functions accept one | |
| argument: a callback, `done`, to be invoked after the hook function is | |
| complete. Hook functions are invoked with `this` bound to the associated | |
| Fastify instance. | |
| This is an alternative to `fastify.server.on('listening', () => {})`. | |
| ```js | |
| // callback style | |
| fastify.addHook('onListen', function (done) { | |
| // Some code | |
| const err = null; | |
| done(err) | |
| }) | |
| // or async/await style | |
| fastify.addHook('onListen', async function () { | |
| // Some async code | |
| }) | |
| ``` | |
| > **Note** | |
| > This hook will not run when the server is started using `fastify.inject()` or `fastify.ready()` | |
| ### onClose | |
| <a id="on-close"></a> | |
| Triggered when `fastify.close()` is invoked to stop the server, after all in-flight | |
| HTTP requests have been completed. | |
| It is useful when [plugins](./Plugins.md) need a "shutdown" event, for example, | |
| to close an open connection to a database. | |
| The hook function takes the Fastify instance as a first argument, | |
| and a `done` callback for synchronous hook functions. | |
| ```js | |
| // callback style | |
| fastify.addHook('onClose', (instance, done) => { | |
| // Some code | |
| done() | |
| }) | |
| // or async/await style | |
| fastify.addHook('onClose', async (instance) => { | |
| // Some async code | |
| await closeDatabaseConnections() | |
| }) | |
| ``` | |
| ### preClose | |
| <a id="pre-close"></a> | |
| Triggered when `fastify.close()` is invoked to stop the server, before all in-flight | |
| HTTP requests have been completed. | |
| It is useful when [plugins](./Plugins.md) have set up some state attached | |
| to the HTTP server that would prevent the server to close. | |
| _It is unlikely you will need to use this hook_, | |
| use the [`onClose`](#onclose) for the most common case. | |
| ```js | |
| // callback style | |
| fastify.addHook('preClose', (done) => { | |
| // Some code | |
| done() | |
| }) | |
| // or async/await style | |
| fastify.addHook('preClose', async () => { | |
| // Some async code | |
| await removeSomeServerState() | |
| }) | |
| ``` | |
| ### onRoute | |
| <a id="on-route"></a> | |
| Triggered when a new route is registered. Listeners are passed a [`routeOptions`](./Routes.md#routes-options) | |
| object as the sole parameter. The interface is synchronous, and, as such, the | |
| listeners are not passed a callback. This hook is encapsulated. | |
| ```js | |
| fastify.addHook('onRoute', (routeOptions) => { | |
| //Some code | |
| routeOptions.method | |
| routeOptions.schema | |
| routeOptions.url // the complete URL of the route, it will include the prefix if any | |
| routeOptions.path // `url` alias | |
| routeOptions.routePath // the URL of the route without the prefix | |
| routeOptions.bodyLimit | |
| routeOptions.logLevel | |
| routeOptions.logSerializers | |
| routeOptions.prefix | |
| }) | |
| ``` | |
| If you are authoring a plugin and you need to customize application routes, like | |
| modifying the options or adding new route hooks, this is the right place. | |
| ```js | |
| fastify.addHook('onRoute', (routeOptions) => { | |
| function onPreSerialization(request, reply, payload, done) { | |
| // Your code | |
| done(null, payload) | |
| } | |
| // preSerialization can be an array or undefined | |
| routeOptions.preSerialization = [...(routeOptions.preSerialization || []), onPreSerialization] | |
| }) | |
| ``` | |
| To add more routes within an onRoute hook, the routes must | |
| be tagged correctly. The hook will run into an infinite loop if | |
| not tagged. The recommended approach is shown below. | |
| ```js | |
| const kRouteAlreadyProcessed = Symbol('route-already-processed') | |
| fastify.addHook('onRoute', function (routeOptions) { | |
| const { url, method } = routeOptions | |
| const isAlreadyProcessed = (routeOptions.custom && routeOptions.custom[kRouteAlreadyProcessed]) || false | |
| if (!isAlreadyProcessed) { | |
| this.route({ | |
| url, | |
| method, | |
| custom: { | |
| [kRouteAlreadyProcessed]: true | |
| }, | |
| handler: () => {} | |
| }) | |
| } | |
| }) | |
| ``` | |
| For more details, see this [issue](https://github.com/fastify/fastify/issues/4319). | |
| ### onRegister | |
| <a id="on-register"></a> | |
| Triggered when a new plugin is registered and a new encapsulation context is | |
| created. The hook will be executed **before** the registered code. | |
| This hook can be useful if you are developing a plugin that needs to know when a | |
| plugin context is formed, and you want to operate in that specific context, thus | |
| this hook is encapsulated. | |
| **Note:** This hook will not be called if a plugin is wrapped inside | |
| [`fastify-plugin`](https://github.com/fastify/fastify-plugin). | |
| ```js | |
| fastify.decorate('data', []) | |
| fastify.register(async (instance, opts) => { | |
| instance.data.push('hello') | |
| console.log(instance.data) // ['hello'] | |
| instance.register(async (instance, opts) => { | |
| instance.data.push('world') | |
| console.log(instance.data) // ['hello', 'world'] | |
| }, { prefix: '/hola' }) | |
| }, { prefix: '/ciao' }) | |
| fastify.register(async (instance, opts) => { | |
| console.log(instance.data) // [] | |
| }, { prefix: '/hello' }) | |
| fastify.addHook('onRegister', (instance, opts) => { | |
| // Create a new array from the old one | |
| // but without keeping the reference | |
| // allowing the user to have encapsulated | |
| // instances of the `data` property | |
| instance.data = instance.data.slice() | |
| // the options of the new registered instance | |
| console.log(opts.prefix) | |
| }) | |
| ``` | |
| ## Scope | |
| <a id="scope"></a> | |
| Except for [onClose](#onclose), all hooks are encapsulated. This means that you | |
| can decide where your hooks should run by using `register` as explained in the | |
| [plugins guide](../Guides/Plugins-Guide.md). If you pass a function, that | |
| function is bound to the right Fastify context and from there you have full | |
| access to the Fastify API. | |
| ```js | |
| fastify.addHook('onRequest', function (request, reply, done) { | |
| const self = this // Fastify context | |
| done() | |
| }) | |
| ``` | |
| Note that the Fastify context in each hook is the same as the plugin where the | |
| route was registered, for example: | |
| ```js | |
| fastify.addHook('onRequest', async function (req, reply) { | |
| if (req.raw.url === '/nested') { | |
| assert.strictEqual(this.foo, 'bar') | |
| } else { | |
| assert.strictEqual(this.foo, undefined) | |
| } | |
| }) | |
| fastify.get('/', async function (req, reply) { | |
| assert.strictEqual(this.foo, undefined) | |
| return { hello: 'world' } | |
| }) | |
| fastify.register(async function plugin (fastify, opts) { | |
| fastify.decorate('foo', 'bar') | |
| fastify.get('/nested', async function (req, reply) { | |
| assert.strictEqual(this.foo, 'bar') | |
| return { hello: 'world' } | |
| }) | |
| }) | |
| ``` | |
| Warn: if you declare the function with an [arrow | |
| function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions), | |
| the `this` will not be Fastify, but the one of the current scope. | |
| ## Route level hooks | |
| <a id="route-hooks"></a> | |
| You can declare one or more custom lifecycle hooks ([onRequest](#onrequest), | |
| [onResponse](#onresponse), [preParsing](#preparsing), | |
| [preValidation](#prevalidation), [preHandler](#prehandler), | |
| [preSerialization](#preserialization), [onSend](#onsend), | |
| [onTimeout](#ontimeout), and [onError](#onerror)) hook(s) that will be | |
| **unique** for the route. If you do so, those hooks are always executed as the | |
| last hook in their category. | |
| This can be useful if you need to implement authentication, where the | |
| [preParsing](#preparsing) or [preValidation](#prevalidation) hooks are exactly | |
| what you need. Multiple route-level hooks can also be specified as an array. | |
| ```js | |
| fastify.addHook('onRequest', (request, reply, done) => { | |
| // Your code | |
| done() | |
| }) | |
| fastify.addHook('onResponse', (request, reply, done) => { | |
| // your code | |
| done() | |
| }) | |
| fastify.addHook('preParsing', (request, reply, done) => { | |
| // Your code | |
| done() | |
| }) | |
| fastify.addHook('preValidation', (request, reply, done) => { | |
| // Your code | |
| done() | |
| }) | |
| fastify.addHook('preHandler', (request, reply, done) => { | |
| // Your code | |
| done() | |
| }) | |
| fastify.addHook('preSerialization', (request, reply, payload, done) => { | |
| // Your code | |
| done(null, payload) | |
| }) | |
| fastify.addHook('onSend', (request, reply, payload, done) => { | |
| // Your code | |
| done(null, payload) | |
| }) | |
| fastify.addHook('onTimeout', (request, reply, done) => { | |
| // Your code | |
| done() | |
| }) | |
| fastify.addHook('onError', (request, reply, error, done) => { | |
| // Your code | |
| done() | |
| }) | |
| fastify.route({ | |
| method: 'GET', | |
| url: '/', | |
| schema: { ... }, | |
| onRequest: function (request, reply, done) { | |
| // This hook will always be executed after the shared `onRequest` hooks | |
| done() | |
| }, | |
| // // Example with an async hook. All hooks support this syntax | |
| // | |
| // onRequest: async function (request, reply) { | |
| // // This hook will always be executed after the shared `onRequest` hooks | |
| // await ... | |
| // } | |
| onResponse: function (request, reply, done) { | |
| // this hook will always be executed after the shared `onResponse` hooks | |
| done() | |
| }, | |
| preParsing: function (request, reply, done) { | |
| // This hook will always be executed after the shared `preParsing` hooks | |
| done() | |
| }, | |
| preValidation: function (request, reply, done) { | |
| // This hook will always be executed after the shared `preValidation` hooks | |
| done() | |
| }, | |
| preHandler: function (request, reply, done) { | |
| // This hook will always be executed after the shared `preHandler` hooks | |
| done() | |
| }, | |
| // // Example with an array. All hooks support this syntax. | |
| // | |
| // preHandler: [function (request, reply, done) { | |
| // // This hook will always be executed after the shared `preHandler` hooks | |
| // done() | |
| // }], | |
| preSerialization: (request, reply, payload, done) => { | |
| // This hook will always be executed after the shared `preSerialization` hooks | |
| done(null, payload) | |
| }, | |
| onSend: (request, reply, payload, done) => { | |
| // This hook will always be executed after the shared `onSend` hooks | |
| done(null, payload) | |
| }, | |
| onTimeout: (request, reply, done) => { | |
| // This hook will always be executed after the shared `onTimeout` hooks | |
| done() | |
| }, | |
| onError: (request, reply, error, done) => { | |
| // This hook will always be executed after the shared `onError` hooks | |
| done() | |
| }, | |
| handler: function (request, reply) { | |
| reply.send({ hello: 'world' }) | |
| } | |
| }) | |
| ``` | |
| **Note**: both options also accept an array of functions. | |
| ## Using Hooks to Inject Custom Properties | |
| <a id="using-hooks-to-inject-custom-properties"></a> | |
| You can use a hook to inject custom properties into incoming requests. | |
| This is useful for reusing processed data from hooks in controllers. | |
| A very common use case is, for example, checking user authentication based | |
| on their token and then storing their recovered data into | |
| the [Request](./Request.md) instance. This way, your controllers can read it | |
| easily with `request.authenticatedUser` or whatever you want to call it. | |
| That's how it might look like: | |
| ```js | |
| fastify.addHook('preParsing', async (request) => { | |
| request.authenticatedUser = { | |
| id: 42, | |
| name: 'Jane Doe', | |
| role: 'admin' | |
| } | |
| }) | |
| fastify.get('/me/is-admin', async function (req, reply) { | |
| return { isAdmin: req.authenticatedUser?.role === 'admin' || false } | |
| }) | |
| ``` | |
| Note that `.authenticatedUser` could actually be any property name | |
| chosen by yourself. Using your own custom property prevents you | |
| from mutating existing properties, which | |
| would be a dangerous and destructive operation. So be careful and | |
| make sure your property is entirely new, also using this approach | |
| only for very specific and small cases like this example. | |
| Regarding TypeScript in this example, you'd need to update the | |
| `FastifyRequest` core interface to include your new property typing | |
| (for more about it, see [TypeScript](./TypeScript.md) page), like: | |
| ```ts | |
| interface AuthenticatedUser { /* ... */ } | |
| declare module 'fastify' { | |
| export interface FastifyRequest { | |
| authenticatedUser?: AuthenticatedUser; | |
| } | |
| } | |
| ``` | |
| Although this is a very pragmatic approach, if you're trying to do | |
| something more complex that changes these core objects, then | |
| consider creating a custom [Plugin](./Plugins.md) instead. | |
| ## Diagnostics Channel Hooks | |
| One [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html) | |
| publish event, `'fastify.initialization'`, happens at initialization time. The | |
| Fastify instance is passed into the hook as a property of the object passed in. | |
| At this point, the instance can be interacted with to add hooks, plugins, | |
| routes, or any other sort of modification. | |
| For example, a tracing package might do something like the following (which is, | |
| of course, a simplification). This would be in a file loaded in the | |
| initialization of the tracking package, in the typical "require instrumentation | |
| tools first" fashion. | |
| ```js | |
| const tracer = /* retrieved from elsewhere in the package */ | |
| const dc = require('node:diagnostics_channel') | |
| const channel = dc.channel('fastify.initialization') | |
| const spans = new WeakMap() | |
| channel.subscribe(function ({ fastify }) { | |
| fastify.addHook('onRequest', (request, reply, done) => { | |
| const span = tracer.startSpan('fastify.request.handler') | |
| spans.set(request, span) | |
| done() | |
| }) | |
| fastify.addHook('onResponse', (request, reply, done) => { | |
| const span = spans.get(request) | |
| span.finish() | |
| done() | |
| }) | |
| }) | |
| ``` | |
| > **Note:** The TracingChannel class API is currently experimental and may undergo | |
| > breaking changes even in semver-patch releases of Node.js. | |
| Five other events are published on a per-request basis following the | |
| [Tracing Channel](https://nodejs.org/api/diagnostics_channel.html#class-tracingchannel) | |
| nomenclature. The list of the channel names and the event they receive is: | |
| - `tracing:fastify.request.handler:start`: Always fires | |
| - `{ request: Request, reply: Reply, route: { url, method } }` | |
| - `tracing:fastify.request.handler:end`: Always fires | |
| - `{ request: Request, reply: Reply, route: { url, method }, async: Bool }` | |
| - `tracing:fastify.request.handler:asyncStart`: Fires for promise/async handlers | |
| - `{ request: Request, reply: Reply, route: { url, method } }` | |
| - `tracing:fastify.request.handler:asyncEnd`: Fires for promise/async handlers | |
| - `{ request: Request, reply: Reply, route: { url, method } }` | |
| - `tracing:fastify.request.handler:error`: Fires when an error occurs | |
| - `{ request: Request, reply: Reply, route: { url, method }, error: Error }` | |
| The object instance remains the same for all events associated with a given | |
| request. All payloads include a `request` and `reply` property which are an | |
| instance of Fastify's `Request` and `Reply` instances. They also include a | |
| `route` property which is an object with the matched `url` pattern (e.g. | |
| `/collection/:id`) and the `method` HTTP method (e.g. `GET`). The `:start` and | |
| `:end` events always fire for requests. If a request handler is an `async` | |
| function or one that returns a `Promise` then the `:asyncStart` and `:asyncEnd` | |
| events also fire. Finally, the `:error` event contains an `error` property | |
| associated with the request's failure. | |
| These events can be received like so: | |
| ```js | |
| const dc = require('node:diagnostics_channel') | |
| const channel = dc.channel('tracing:fastify.request.handler:start') | |
| channel.subscribe((msg) => { | |
| console.log(msg.request, msg.reply) | |
| }) | |
| ``` | |