À la découverte de…Fastify

Node.js permet nativement de créer un serveur HTTP, mais il est de notre responsabilité de devoir gérer le routage, le formatage des données ou encore la gestion des erreurs. Bref, on ne va pas se mentir, la plupart du temps nous utilisons des frameworks tels que le populaire Express.js. Mais un autre framework commence à faire de plus en plus parler de lui, il s’agit de Fastify.

Note : Puisqu’il s’agit d’un article de découverte, je ne rentre pas dans les détails et les exemples sont les plus simples possibles pour rendre l’article accessible à tous. Je ferais peut-être un article plus poussé sur Fastify dans le futur.

Fastify c’est quoi ?

Fastify est un framework web léger pour Node.js, inspiré de Hapi, Restify et Express. Comme son nom l’indique, Fastify est rapide, c’est même le framework web le plus rapide de l’écosystème Node.js. Mais outre sa vitesse, sa popularité vient de l’excellente expérience développeur qu’il offre et de son système de plugins simple, mais efficace.

Entrons tout de suite dans le vif du sujet et installons Fastify :

Créons ensuite un fichier server.js :

Puis lançons notre serveur :

Enfin, vérifions que tout fonctionne correctement :

Vous devriez voir s’afficher {"hello":"world"}.

Si vous avez déjà utilisé Express ou tout autre framework web, vous ne devriez pas être perdu.

Déclaration de routes

La déclaration de route peut se faire de deux façons. La première en utilisant la méthode fastify.route(options) :

Ou la seconde en utilisant directement les méthodes correspondantes aux méthodes HTTP :

  • fastify.get(path, [options], handler)
  • fastify.head(path, [options], handler)
  • fastify.post(path, [options], handler)
  • fastify.put(path, [options], handler)
  • fastify.delete(path, [options], handler)
  • fastify.options(path, [options], handler)
  • fastify.patch(path, [options], handler)

Par exemple :

async/ await

La plupart des autres frameworks (notamment Express) ne proposent pas un support async/await c’est-à-dire lorsque vous écrivez un handler asynchrone d’une route, vous êtes obligé d’utiliser un try/catch sous peine de vous retrouver avec une erreur UnhandledPromiseRejectionWarning.

Voici un exemple avec Express :

Et voici le même exemple avec Fastify :

Si une erreur se produit, Fastify va automatiquement la transmettre au handler d’erreurs.

Si vous voulez en savoir plus, je vous invite à aller lire la documentation concernant les routes.

Validation et sérialisation des données

Fastify utilise JSON schema pour valider les données reçues sur les différentes routes, mais également pour sérialiser le corps des réponses HTTP. Pour cela il fait appel à deux libraires :

  • Ajv pour la validation des requêtes ;
  • fast-json-stringify pour la sérialisation du corps des réponses qui permet entre autres d’améliorer les performances.

Bien que ce ne soit pas obligatoire, il est vivement recommandé d’utiliser la validation et la sérialisation des données.

Validation

La validation des données des requêtes peut s’effectuer sur les quatre propriétés suivantes :

  • body : permet de valider le corps de la requête POST ou PUT;
  • querystring ou query : permet de valider les “query strings” (ce sont les paramètres qui se trouvent après le caractère ? par exemple ?foo=hello&bar=world)
  • params : permet de valider les paramètres de la route (par exemple dans la route /posts/:idid est un paramètre) ;
  • headers : permet de valider les en-têtes de la requête.

Pour définir le schéma de validation de ces propriétés, il suffit d’ajouter l’option schema à la route :

Testons maintenant notre route avec un paramètre id incorrect :

On obtient l’erreur HTTP suivante :

Voyons maintenant avec le paramètre “query string” start_at incorrect :

On obtient l’erreur HTTP suivante :

Il est également possible d’utiliser d’autres librairies de validation telle que Joi par exemple.

Sérialisation

Le schéma de sérialisation permet d’améliorer les performances, mais également d’empêcher de renvoyer des données sensibles. Comme pour la validation, il suffit d’ajouter l’option schema à la route :

Testons maintenant notre route :

On obtient la réponse suivante :

On remarque que la propriété password n’est pas contenue dans la réponse puisque celle-ci ne fait pas partie du schéma.

Si vous voulez en savoir plus concernant la validation et la sérialisation des données, direction la documentation.

Hooks

Fastify propose de nombreux hooks permettant d’interagir avec le cycle de vie du serveur et/ou du cycle de vie des requêtes et des réponses. Les hooks sont simplement des méthodes que l’on associe à des événements comme vous avez sûrement l’habitude de le faire.

Les hooks liés au serveur

Il existe quatre hooks liés au serveur :

  • onReady : déclenché quand le serveur a fini son initialisation juste avant que celui-ci soit en écoute ;
  • onClose : déclenché lorsque la méthode close est appelée pour arrêter le serveur ;
  • onRoute : déclenché lors de l’enregistrement d’une route ;
  • onRegister : déclenché lors de l’enregistrement d’un plugin.

Les hooks liés aux requêtes et réponses

Il existe neuf hooks liés aux requêtes et réponses :

  • onRequest : déclenché quand une requête arrive sur le serveur ;
  • preParsing : déclenché avant de parser la requête. Vous pouvez, dans ce hook, modifier le payload de la requête ;
  • preValidation : déclenché avant la validation de la requête ;
  • preHandler : déclenché avant l’exécution du handler de la requête ;
  • preSerialization : déclenché avant la sérialisation de la réponse ;
  • onSend : déclenché avant l’envoi de la réponse. Ce hook permet par exemple de changer le payload de la réponse ;
  • onError : déclenché lorsqu’une erreur se produit ;
  • onResponse : déclenché lorsque la réponse est envoyée ;
  • onTimeout : déclenché lorsque la requête a expiré. L’option connectionTimeout doit être définie lors de la création du serveur pour que ce hook se déclenche.

Eh ben ça en fait des hooks ! L’ajout d’un hook s’effectue via la méthode addHook de l’instance du serveur. Par exemple :

Decorators

Les décorators permettent d’ajouter des propriétés ou des méthodes à l’instance du serveur ou aux objets correspondant aux requêtes et aux réponses. Javascript étant un langage dynamique, il est possible d’ajouter facilement des propriétés à un objet :

On serait donc tenté de faire la même chose avec l’instance de notre serveur ou avec les requêtes et réponses :

Mais faire de cette façon empêche le moteur JavaScript V8, d’effectuer des optimisations. À la place il faut faire appel, aux méthodes decorate, decorateRequest et decorateReply pour respectivement ajouter des propriétés à l’instance du serveur, à l’objet correspondant à la requête et à celui correspondant à la réponse.

Modifions l’exemple précédent en utilisant les méthodes que je viens d’évoquer :

Système de plugins

L’une des forces de Fastify est son système de plugins très simple à prendre en main. Un plugin est simplement une fonction contenant des decorators, des hooks, des routes ou même d’autres plugins et se présentant sous la forme suivante :

Avec en paramètre :

  • instance : l’instance du serveur ;
  • opts : un objet contenant les options ;
  • done : une fonction de callback a appelé une fois le plugin initialisé.

Il suffit ensuite d’enregistrer le plugin via la méthode register comme ceci :

L’encapsulation

Vous remarquez que l’on passe l’instance du serveur en premier paramètre de la fonction du plugin. Cette instance va nous permettre d’ajouter des hooks, des decorators, des routes ou même d’autres plugins. Mais tout ce que vous enregistrez à l’intérieur d’un plugin à l’exception des routes, ne sera pas accessible à l’extérieur de celui-ci. Pour faire simple, ce qui se passe à l’intérieur du plugin reste à l’intérieur du plugin !

Voyons un exemple tout simple pour comprendre :

Nous avons deux routes, /hello-plugin et /hello. Testons la première :

Nous avons le message HELLO, WORLD ! en majuscule qui s’affiche. Testons maintenant la seconde :

Cette fois-ci nous avons le message Hello, World ! en minuscule qui s’affiche. Le hook présent dans le plugin ne s’applique qu’aux routes contenues dans celui-ci.

Mais il arrive parfois que l’on ait besoin de rendre accessibles certains decorators ou hooks à l’extérieur d’un plugin. Pour cela il suffit d’utiliser le package fastify-plugin :

Reprenons notre exemple précédent en utilisant fastify-plugin :

Si nous refaisons une requête sur la route http://localhost:3000/hello nous avons bien le message HELLO, WORLD ! en majuscule qui s’affiche. Le hook s’applique bien sur les routes présentes en dehors du plugin.

L’exemple que j’ai donné est vraiment sommaire et n’est pas forcement très utile mais c’est pour que vous compreniez le fonctionnement de l’encapsulation de Fastify.

Ecosystème

Fastify dispose de nombreux plugins créés par la communauté. Vous retrouverez la plupart des fonctionnalités bien connues comme la prise en charge de l’authentification par JWT, la documentation des routes via Swagger/OpenAPI ou encore la prise en charge de CORS. N’hésitez pas à aller voir la liste des plugins sur la documentation officielle et d’aller jeter un oeil au code source.

Benchmark

Je vous ai dit en début d’article que Fastify était le framework web Node.js le plus rapide du marché. Mais à quel point ? Je vous laisse juger par vous-même avec ce benchmark issu du site officiel :

FrameworkNombre de requêtes par seconde

Fastify

64683 req/sec
Koa41324 req/sec

Restify

34424 req/sec
Hapi30830 req/sec
Express11795 req/sec

Pour finir…

Fastify est l’un des frameworks les mieux maintenus à l’heure actuelle et dispose d’une communauté très active. Le développement est également soutenu par la fondation OpenJS qui est également derrière Node.js. Cela fait maintenant plus d’un an que j’utilise Fastify et c’est pour moi l’un des frameworks web avec lequel je prends le plus de plaisir à travailler du fait de sa simplicité et de son système de plugins qui offre de nombreuses possibilités.

Nous avons fait dans cet article, qu’un bref tour d’horizon de Fastify, je vous laisse le soin de poursuivre la découverte en allant lire la documentation officielle qui est pour le coup vraiment bien fichue.

Si des petits articles de découvertes de librairies/frameworks comme celui-ci vous plaît, n’hésitez pas à me le dire !


Annonces partenaire

Je suis lead developer dans une boîte spécialisée dans l'univers du streaming/gaming, et en parallèle, je m'éclate en tant que freelance. Passionné par l'écosystème JavaScript, je suis un inconditionnel de Node.js depuis 2011. J'adore échanger sur les nouvelles tendances et partager mon expérience avec les autres développeurs. Si vous avez envie de papoter, n'hésitez pas à me retrouver sur Twitter, m'envoyer un petit email ou même laisser un commentaire.

2 commentaires

  1. Bonjour, C’est parfois un peu compliqué pour comprendre, dans le site même de Fastify, ce qui est un fichier server.js ou ce qui est un fichier routes.js, etc.
    L’utilisation de fichiers plugins portant la mention “module.exports = ” correspond généralement au nom du fichier (.js).
    Le codage, ici, que vous proposez en exemple pour les plugins peut s’inclure directement à l’intérieur dans le fichier server.js .
    Si l’on veut le placer à l’intérieur d’un module, pour faire un fichier myPlugin.js . Si je comprends bien le didacticiel de Fastify et votre explication, je dois placer toute la portée du plugin dans le fichier, et ajouter “module.exports = myPlugin.js” ?
    Comment dois-je appeler ce plugin dans le fichier server.js afin qu’il y soit utilisé?
    Que dois-je placer en en-tête de fichier de plugin, pour ce qui est des fichiers requis (const n = require(‘…’), car si je dénomme par exemple mon instance “app”, dans le fichier server je vais avoir “const app = require(‘fastify’)()” , mais si j’utilise ce “app” en tant que nom d’instance, Node va me dire que c’est une variable non-définie, et il va falloir donc que je redéfinisse de la même façon app dans ce fichier plugin? Si je veux appeler mon instance “desapp”, par exemple, dans le fichier plugin.js , est-ce que ça va poser un problème dans le fichier server pour l’identification de l’instance?
    La question que je me pose est que si je définis une instance “app” dans le fichier server puis une instance “app” dans un fichier plugin, alors, je génère la même instance ou bien je génère deux instance que je dois spécifier comme identiques dans le fichier server?
    Ca fait trois questions, en fait, de ma part.
    Mon fichier server contient la totalité des routes, avec quelques hooks qui semblent fonctionnels quand je teste mon instance.
    Je voudrais cependant ajouter un plugin contenant la fonction writeFile tel que le site O’Reilly en propose un exemple dans l’utilisation des FileSystem (leur fonction s’appelle on Fs(fs), en fait, si vous regardez leur site).
    Merci.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.