Fastify, le ragioni di una scelta

Davide D'Antonio
weBeetle
Published in
9 min readJul 5, 2019

Fastify è un framework per Node.js, ideato a Bologna da Matteo Collina e Tomas della Vedova, altamente focalizzato sulla fornitura della migliore esperienza di sviluppo con il minimo overhead ed una potente architettura a plugin. Il suo sviluppo è ispirato ad Hapi ed Express e, per quanto ne sappiamo, è uno dei framework web più veloci attualmente in circolazione.

Perchè Fastify?

La ragione per cui abbiamo optato per questo framework in Webeetle è molto semplice: Fastify è veloce! Sfruttando le prestazioni di Node.js, Fastify è stato costruito da zero per essere il più veloce possibile. Tutto il codice utilizzato per i benchmarks è disponibile su GitHub, e di seguito potete dare uno sguardo ai risultati dei benchmarks effettuati per i framework Node.js più famosi in circolazione ﴾a parità di hardware﴿, su un applicazione che restituisce un JSON ‘{ hello: ‘world’ }’

Fastify benchmarks

Fastify utilizza fast-json-stringify per raddoppiare il throughput del rendering di un JSON e find-my-way per ridurre il routing di un fattore pari a 10 rispetto alle alternative.

Plugin completamente incapsulati

La seconda ragione per cui abbiamo scelto questo framework è che offre un modello a plugins simile a quello di Hapi. Aggiunge l’incapsulamento completo dei plugins in modo che ognuno di essi possa utilizzare le proprie dipendenze e gli hook di cui hanno bisogno. Ciò consente un maggiore riutilizzo e disaccoppiamento del software. Inoltre, il modello a plugin si basa su blocchi rientranti e basati su grafi, gestisce perfettamente il codice asincrono e garantisce l’ordine di caricamento e l’ordine di chiusura degli stessi.

Fastify ha una vasta gamma di plugins già sviluppati, compresi quelli per il rendering di modelli, l’integrazione di React, il supporto GraphQL, la pubblicazione di file statici e driver di database.

Hello, Fastify!

Iniziamo dall’installazione di questo fantastico framework. Da console digitiamo:

$ mkdir fastify-demo
$ cd fastify-demo
$ npm init -y
$ npm install fastify

Successivamente creiamo un file index.js, e scriviamo il codice sorgente del nostro primo server:

// file index.js
const fastify = require('fastify')()
fastify.get('/', (request, reply) => {
reply.send({ ciao: 'Fastify' })
})
fastify.listen(3000, (err, address) => {
if (err) {
fastify.log.error(err)
process.exit(1)
}
fastify.log.info(`Server in ascolto su ${address}`)
})

Cosa abbiamo fatto?

Riga 1. Instanziamo Fastify

const fastify = require('fastify')()

Importa il framework Fastify nel nostro progetto. La nostra istanza server ora risiede nella variabile fastify. È possibile aggiungere molte opzioni quanto istanziamo Fastify. Per esempio:

const fastifyWithOptions = require('fastify')({
logger: {
prettyPrint: true
}
})

Grazie al logger Pino, insieme a Pino Colada, questa opzione rende l’output della console facile da leggere e colorato. Date uno sguardo alla documentazione di Pino per conoscere altre opzioni è possibile dare in input all’istanza di Fastify.

Riga 3–5. La nostra prima rotta

fastify.get('/', (request, reply) => {
reply.send({ ciao: 'Fastify' })
})

Partendo dalla riga 3 fino alla 5 è stata definita una rotta. Fastify supporta due metodi di definizione delle rotte: il metodo abbreviato, come quello che avete appena visto, ed un metodo generale .route come mostrato di seguito.

fastify.route({
method: 'GET',
url: '/',
handler: function (request, reply) {
reply.send({ ciao: 'Fastify' })
}
})

Entrambe queste implementazioni fanno esattamente la stessa cosa ed hanno le stesse prestazioni, quindi utilizzate semplicemente quella che vi piace di più. È possibile specificare diverse opzioni su una rotta specifica. Tra le più importanti:

  • schema: un oggetto contenente gli schemi per la richiesta e la risposta. Devono essere nel formato [JSON Schema](http://json-schema.org/)
  • preHandler (request, reply, done): una funzione chiamata appena prima del gestore della richiesta, potrebbe anche essere una matrice di funzioni.
  • bodyLimit: impedisce al parser del body di analizzare richieste più grandi di questo numero di byte. Deve essere un numero intero. È inoltre possibile impostare questa opzione a livello globale quando si crea per la prima volta l’istanza Fastify con Fastify. Il valore predefinito è 1MB.

Potete dare uno sguardo all’intera lista di parametri dalla documentazione ufficiale di Fastify.

Riga 7–13. Il nostro server

In questo blocco di codice, infine, viene effettuato l’avvio dell’istanza Fastify su localhost:3000. Questo è l’ultimo passaggio necessario per creare la nostra istanza di Fastify. Internamente questo metodo attenderà il .ready() (che viene invocato dopo aver caricato i plugin). Non è possibile definire nuove rotte dopo aver chiamato il metodo .listen().

Pronti, via! Avviamo il server!

Una volta terminata l’implementazione di questo piccolo esempio, dal terminale basta lanciare il seguente comando:

$ node index.js

Et voilà! Navigando con il browser all’indirizzo http://localhost:3000/ vi verrà restituita la seguente stringa JSON { ciao: ‘Fastify’ }. Niente di più semplice!

Gli Hook

Gli hook vengono registrati con il metodo fastify.addHook e consentono di restare in ascolto di eventi specifici nell’applicazione o nel ciclo di vita di una richiesta/risposta. Bisogna necessariamente registrare un hook prima che l’evento venga innescato altrimenti l’evento viene perso.

Usando gli hook possiamo interagire direttamente con il ciclo di vita di Fastify. Esistono sette diversi hook che è possibile utilizzare (in ordine di esecuzione):

  • onRequest
  • preParsing
  • preValidation
  • preHandler
  • preSerialization
  • onError
  • onSend
  • onResponse

er il loro utilizzo vi consiglio vivamente di leggere la documentazione ufficiale.

I Decorator

Questa è una delle features più interessanti del framework! Se abbiamo la necessità di aggiungere una funzionalità, o una configurazione, che verrà utilizzata in modo trasversale in tutto il nostro sistema, potete utilizzare l’API decorate. Il suo utilizzo è semplicissimo:

fastify.decorate('utility' _ => {
// qualcosa di veramente utile!!
})

oppure, come già accennato in precedenza possiamo aggiungere banalmente un oggetto:

fastify.decorate('config', {
db: 'file.db',
port: 3000
})

Una volta “decorata” l’istanza Fastify, è possibile accedervi in qualsiasi momento utilizzando il nome datogli come parametro:

fastify.utility()
console.log(fastify.config.db)

I Plugins

Come già anticipato, una delle migliori caratteristiche di Fastify sono i plugins. Fastify, come lo stesso fondatore Matteo Collina ha detto più e più volte nei suoi talk, è stato progettato per essere un framework estremamente modulare. Infatti, così come in JavaScript tutto un oggetto, in Fastify tutto è un plugin. Il sistema di plugins di Fastify è un modello di incapsulamento che consente di scomporre l’applicazione in più microservizi in qualsiasi momento. Senza dover effettuare il refactoring dell’intera applicazione.

Per aggiungere un nuovo plugin in Fastify avete un unico metodo API da usare: register. Questo metodo crea un nuovo contesto in Fastify, questo vuol dire che se cambiate qualsiasi cosa alla vostra istanza Fastify, questi cambiamenti non si rifletteranno agli antenati del contesto.

fastify.register(
require('il-mio-plugin'),
{ options }
)

I plugin devono esporre una singola funzione con la seguente firma:

module.exports = function (fastify, options, next) {}

Dove fastify è l’istanza incapsulata di Fastify, options è un oggetto contenente le opzioni e next è la funzione che verrà richiamata quando il plugin è stato correttamente caricato. Si può pensare al modello dei plugin di Fastify come un DAG, e gestisce senza alcun tipo di problema il codice asincrono garantendo l’ordine di caricamento, così come l’ordine di chiusura. All’interno di un plugin potete fare tutto ciò che volete, compreso registrare, un altro plugin. Potete aggiungere nuove rotte, o un decorator e così via. In altre parole:

function myPlugin (fastify, opts, next) {
// aggiungere altri plugin
fastify.register (...)
// aggiugnere un Hook
fastify.addHook(...)
// aggiungere un decorator
fastify.decorate(...)

// aggiungere una rotta
fastify.route(...)
next()
}
module.exports = myPlugin

Possiamo rappresentare graficamente un plugin nel seguente modo:

Fastify plugins DAG

Perfetto! ora conosciamo, più o meno tutti i tools necessari per estendere Fastify!

Se diamo uno sguardo all’immagine precedente, noterete che effettuando un register all’interno di myPlugin di myPlugin2 questo non sarà visibile ai suoi antenati. Quindi, di conseguenza, sarà solo myPlugin, e non l’istanza principale di Fastify, che avrà accesso a tutte le funzionalità esposte da myPlugin2.

C’è in realtà un modo per dire a Fastify di evitare questo comportamento. In che modo? Utilizzando fastify-plugin. Vediamo come:

const fp = require('fastify-plugin')
const myPlugin2 = require('myPlugin2')
function myPlugin (fastify, opts, next) {
fastify.decorate('myPlugin2', myPlugin2)
next()
}
module.exports = fp(myPlugin)

In questo modo, da qualsiasi altro punto della vostra applicazione potete fare qualcosa del genere

fastify.myPlugin2.someUtils()

Di conseguenza, se vogliamo aggiornare l’immagine mostrata in precedenza:

Fastify plugins DAG

create-fastify-app

create-fastify-app è un’utility che ci aiuta a generare o aggiungere plugins ad un progetto Fastify esistente. Ciò significa che puoi facilmente:

  • Generare un progetto Fastify, anche da un determinato file swagger.
  • Generare lo scheletro di servizio in un progetto Fastify esistente.
  • Aggiungere il plugin fastify-cors in un progetto Fastify esistente.
  • Aggiungere il plugin fastify-mongodb in un progetto Fastify esistente.
  • Aggiungere il plugin fastify-redis in un progetto Fastify esistente.
  • Aggiungre il plugin fastify-postgres in un progetto Fastify esistente.

Installare questa utility è molto semplice. Semplicemente dalla riga di comando digitiamo:

$ npm install -g create-fastify-app

Alla fine dell’installazione avremo a disposizione il comando create-fastify-app. Creare un nuovo progetto è veramente semplice:

$ create-fastify-app generate:project -d fastify-demo

A questo punto ci verranno fatte alcune domande alle quali dobbiamo rispondere:

create-fastify-app crea automaticamente tutto ciò di cui abbiamo bisogno per iniziare lo sviluppo della nostra applicazione.

Di default, viene generata la seguente struttura di files e directory:

fastify-demo
├── README.md
├── app
│ ├── app.js
│ ├── plugins
│ │ ├── README.md
│ │ └── support.js
│ └── services
│ ├── README.md
│ ├── hello
│ │ └── index.js
│ └── root.js
├── args.js
├── help
│ └── start.txt
├── package.json
├── server.js
└── test
├── helper.js
├── plugins
│ └── support.test.js
└── services
├── example.test.js
└── root.test.js

La directory app conterrà tutto ciò di cui abbiamo bisogno per sviluppare la nostra applicazione. In particolare conterrà le seguenti directory:

  • plugins: qui possiamo aggiungere tutti i plugins di cui abbiamo bisogno per lo sviluppo della nostra applicazione.
  • sevices: qui possiamo aggiungere tutti gli endpoint.
  • test: qui è dove possiamo dichiarare tutti i test.

Il file package.json ci viene fornito con i seguenti script:

"scripts": {
"test": "tap test/**/*.test.js",
"start": "node server.js",
"dev": "node server.js -l info -P"
}
  • npm test: esegue i test.
  • npm start: avvia il nostro server.
  • npm run dev: avvia il server il modalità dev

Dopo aver fatto ciò, seguiamo le indicazioni forniteci nelle ultime tre righe:

$ cd fastify-demo
$ npm install
$ npm run dev

È possibile definire le seguenti opzioni da riga di comando con node server.js <opzioni>. Ogni opzione ha la corrispondente variabile d’ambiente:

Generare un progetto da un file swagger

Quando generiamo un nuovo progetto, possiamo specificare un file swagger da cui partire. In un progetto API driven questa funzione può essere molto importante perché create-fastify-app genererà in automatico tutti gli endpoint del progetto. Avremo solo l’incarico di implementarli.

Se per esempio diamo in input il file swagger Petstore fornito come esempio su Swagger.io, possiamo vedere che create-fastify-app genera automaticamente un progetto con fastify-swagger già configurato e pronto per l’uso. Se diamo un’occhiata all’endpoint /documentation troveremo qualcosa del genere:

create-fastify-app swagger example

In app/services troveremo la varie directory ed i relativi endpoint, di cui dovremo fornire un implementazione:

./app/services
├── README.md
├── pet
│ ├── index.js
│ └── routes.schema.js
├── root.js
├── store
│ ├── index.js
│ └── routes.schema.js
└── user
├── index.js
└── routes.schema.js

Per concludere

Fastify è un framework valido sia per chi si sta avvicinando all’utilizzo di Node.js, sia per chi vuole cambiare aria o si sente insoddisfatto di quello che utilizza attualmente. I Decorator e l’incapsulamento totale dei plugin sono il vero punto di forza di questo framework, e ci consentono di scrivere moduli software riutilizzabili in modo trasversale nelle nostre applicazioni o di scomporre in microservizi un’applicazione già creata in precedenza, e che sta per diventare un monolite, con il minor effort possibile. Inoltre con l’utilizzo di create-fastify-app riuscirete ad effettuare lo startup di un progetto velocemente, rispondendo solo a qualche domanda. In particolar modo se si parte da uno swagger file. Happy Coding!

--

--

Davide D'Antonio
weBeetle

👨‍🎓Degree in computer science 💑 Married with Milena 🤓 Huge Nerd! 💻 Code lover 👨🏻‍💻 Fullstack developer