flowchart LR
%% Left Request Phase (top to bottom)
subgraph RP1[Request Phase]
direction TB
onRequest[onRequest]
preParsing[preParsing]
preValidation[preValidation]
preHandler[preHandler]
onRequest --> preParsing
preParsing --> preValidation
preValidation --> preHandler
end
%% Middle Route Handler (vertical alignment)
routeHandler[Route Handler]
%% Right Response Phase (top to bottom)
subgraph RP2[Request Phase]
direction TB
preSerialization[preSerialization]
onSend[onSend]
onResponse[onResponse]
preSerialization --> onSend
onSend --> onResponse
end
%% Main flow
RP1 --> routeHandler --> RP2
%% Dashed borders for phases
style RP1 stroke-dasharray: 5 5
style RP2 stroke-dasharray: 5 5
Accelerating Server-Side Development With Fastify
Notes
Chapter 01: What is Fastify?
Components
Main Components:
- root application instance represents the Fastify API at your disposal. It manages and controls the standard Node.js
http.Serverclass and sets all the endpoints and the default behavior for every request and response. - plugin instance is a child object of the application instance, which shares the same interface. It isolates itself from other sibling plugins to let you build independent components that can’t modify other contexts.
Utility Components:
- hooks are functions that act, when needed, during the lifecycle of the applications.
- decorators let you augment the features installed by default on main components.
- parsers are responsible for the request’s payload conversion to primitive types.
Lifecycles
Application lifecycle tracks the status of the application instance and trigger these set of events:
- The
onRouteevent acts when you add an endpoint to the sever instance. - The
onRegisterevent is unique as it performs when a new encapsulated context is created. - The
onReadyevent runs when the application is ready to start listening for income HTTP requests. - The
onCloseevent executes when the server is stopping.
All these events are application hooks.
- The
Request lifecycle defines the flow of every HTTP request that your server will receive. The server will process the request in two phases:
- The routing: This ste p must find the function that must evaluate the request.
- The handling of the request performs a set of events that compose the request lifecycle.
The request triggers the following events:
onRequest: The server receives an HTTP request and routes it to a valid endpoint.preParsinghappens before the evaluation of the request’s body payloadpreValidationruns before applying JSON Schema validation to the request’s parts.preHandlerexecutes before the endpoint handler.preSerializationtakes action before the response payload transformation to a String, Buffer or a Stream in order to be sent to the client.onErroris execute only if an error happens during the request lifecycleonSendis the last change to manipulate the response payload before sending it to the client.onResponseruns after HTTP request has been served.
Application instance methods
app.routeadds a new endpoint to the serverapp.register(plugin)adds plugins to the server instanceapp.readyloads all the applications without listening to the HTTP requestapp.listenstarts the server and loads the applicationapp.closeturns off the server and starts the closing flowapp.injectloads the server until is reaches the ready status and submits a mock HTTP request
The various ways to defined a route
Using an arrow function will prevent you from getting the function context. Without the context, you don’t have the possibility to use the
thiskeyword to access the application instance.
// with arrow function
fastify.get("/function-context", async (req, res) => {
res.send({ helloFrom: this.server.address() });
});
// ➜ curl -s http://localhost:3000/function-context | jq
// {
// "statusCode": 500,
// "error": "Internal Server Error",
// "message": "Cannot read properties of undefined (reading 'server')"
// }- Recommended to avoid using the
reply.send()method as it can only be sent once per handler. If you want to reuse handlers within other handlers it’s best to just return the payload directly.
The Reply Object
Main Methods:
reply.send(payload)will send the response to the client, can be a String, JSON object, Buffer, Stream or Error object. Can be replaced by returning the payload in the handler function.reply.code(number)will set the status code of the response.reply.header(key, value)will add a response header.reply.type(string)shorthand way to define theContent-Type.Methods can be chained together
reply.code(201).send('done)
Fastify “magic”:
Content-Lengthis equal to the length of the output payload unless set manually.Content-Typeresolves totext/plainfor strings,application/jsonfor JSON objects,application/octet-streamfor streams and buffers.- auto set the status code to
200for success and500for errors - Will try calling
payload.toJSON()if the payload is aClassobject.
Configuration Types
- Server Options provide settings for the Fastify framework to start and support your app.
- Plugin config provides all the params to config your plugins
- App config defines your endpoint settings
Chapter 02: The Plugin System and the Boot Process
My main takeaway from this chapter is everything in Fastify is basically a plugin. Plugins can have infinite nesting and every level of depth will create a new encapsulated context.
Chapter 03: Working with Routes
you might feel overwhelmed by having to understand the functions executed when a request reaches an endpoint…To reduce the stress, Fastify has a couple of debugging outputs and techniques that are helpful to unravel a complex codebase
Honestly I get this feeling of “What the hell is executing?” for any complex backend app I have jumped into. Seems like so many backend frameworks have so much “indirection” it can be hard to understand which cod gets executed when a certain endpoint is hit.
Now it’s usually for good reason as it promotes DRY code but it makes it hard to follow nonetheless. I use a debug server with breakpoints to understand the life of a request but having these debugging outputs is nice as well.
Chapter 04: Exploring Hooks
flowchart LR
%% Free-floating Error / Timeout box (top middle)
subgraph ERR[ ]
direction TB
onError[onError]
onTimeout[onTimeout]
end
style ERR stroke-dasharray: 5 5
- The
preHandlerhook is often the most used hooked by developers, but it shouldn’t be. Only use this hook when accessing or manipulating validated body properties. - The
preSerializationhook is not called when the payload argument is of typestring,Buffer, ornull - The
onSendis the last hook invoked before replying to the client. - The
onResponseis called after reply has already been sent to the client. Therefore you can’t change the payload anymore. However, it’s useful to do things like collect metrics or external services. - The
onErrorhook is triggered only when the server sends an error as the payload to the client. It runs after thecustomErrorHandlerif provided or after the default one integrated into Fastify. Its primary use is to do additional logging or modify the reply headers. Avoid calling thereply.senddirectly.
reply.send in async hooks
Besides throwing an error, there is another way of early exiting from the request phase execution flow at any point. Terminating the chain is just a matter of sending the reply from a hook: this will prevent everything that comes after the current hook from being executed. For example, this can be useful when implementing authentication and authorization logic.
However, there are some quirks when dealing with asynchronous hooks. For example, after calling reply.send to respond from a hook, we need to return the reply object to signal that we are replying from the current hook. The reply-from-hook.cjs example will make everything clear:
We check whether the current user is authorized to access the resource [1]. Then, when the user misses the correct permissions, we reply from the hook directly [2] and return the reply object to signal it [3]. We are sure that the hook chain will stop its execution here, and the user will receive the ‘Unauthorized’ message.
- Funny enough, this exact issue caused some errors on Supabase Storage
Chapter 5: Exploring Validation and Serialization
- Fastify applies the HTTP request part’s validation after executing the
preValidationhooks and before thepreHandlerhooks.
Summary
I found this book to be helpful insight into how Fastify. Would recommend for anyone looking to learn more about Fastify. My only critique is I wish all examples were updated to use TypeScript and ESM syntax.