-
- Intro
-
- Why do an API in Node?
-
- Hapi et Express en principe
- 3.1. Express
- 3.1.1. Philosophie
- 3.1.2. Historique
- 3.1.3. Rythme de développement
- 3.1.4. Communauté
- 3.2. Hapi
- 3.2.1. Philosophie
- 3.2.2. Historique
- 3.2.3. Rythme de développement
- 3.2.4. Communauté
- 3.3. Synthèse et Statiques
-
- Express et Hapi en pratique
- 4.1. Intro
- 4.1.1. Code.show()
- 4.1.2. Description use case exemple
- 4.2. Les composants principaux
- 4.2.1. Au coeur, le serveur
- 4.2.2. Définition des routes
- 4.2.3. Les handleurs de requêtes
- 4.3. Interprétation de la requête
- 4.3.1. Diversité des données à récupérer
- 4.3.2. Processus de collecte des corps
- 4.3.3. Quelques pointeur vers du concret
- 4.4. Création de la réponse
- 4.4.1. Codes de Retour HTTP
- 4.4.2. Utilisation des Headers HTTP
- 4.4.3. Gestion du Corps du message
- 4.5. TODO Express et Hapi au delà du basique
-
- Conclusion: why we focus on Hapi
Ces dernières années l'approche REST est être train de devenir l'architecture incontournable des API. Parallèlement on assiste à une reconfiguration du paysage coté server notamment avec la percée de Node.js qui s'est imposé comme un des principale stack technique.
Dans cette article nous allons creuser comment faire de telles API REST en node.js, ceci au travers des deux frameworks principaux, Express et Hapi.
On présentera dans un premier temps ce qui fait que node.js est une stack technique attrayante pour réaliser les Api, avant de voir les deux frameworks, tant d'un point de vue principes, que d'un point de vue pratique.
Si jamais vous êtes trop impatient de voire du code et de lire la partie technique, vous pouvez toujours passer par ce petit raccourci.
Node est attrayant pour la réalisation de services REST pour plusieurs raisons:
Tout d'abord, Nodejs est nativement très orienté Web, la bibliothèque de serveur HTTP étant intégrée, ainsi que les protocoles de plus bas niveau. De plus json étant devenu le format d'échange standard sur HTTP, format on ne peut plus facile à produire en javascript.
Node.js a bien d'autres atouts pour la réalisation de telles API. Node.js pousse à réaliser des applications modulaires, donc facile à répartir entre plusieurs équipes.
De plus celui-ci est léger, interprété et réactif ce qui permet de fluidifier les développements, et de pratiquer le déploiement continue.
Pour autant ceci ne se fait pas au dépend de la performance, l'approche asynchrone de boucle d'événement au coeur de node et la machine virtuelle v8 ayant fait leurs preuves dans nos navigateur. La scalabilité se fait aisément de manière horizontale sans développement supplémentaire.
Aussi la plateforme est bien vivante avec un support industriel et une forte communauté avec son un environnement de plugins et frameworks considérable.
Preuve de ceci, malgré être relativement jeune, la plateforme Node a déjà été adoptée, utilisée et éprouvée par des grands du Web comme Walmart, Paypal, Linkedin, Yahoo!
Dans l'écosystème Node, deux frameworks se dégagent quant à la réalisation de services REST. Il s'agit d'Express et d'Hapi.
Nous allons dans cette partie donner une vue d'ensemble: parcourir leur cible, leur philosophie sous-jacente ainsi que leur historique, et la communauté qui autour d'eux s'est développée.
Nous laissons de coté Koa, celui-ci étant une revisite d'express à la sauce Es6, ainsi que d'autres frameworks qui se sont construit au dessus d'express.
Express est un minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. Ou comme un cadriciel minimal et flexible.
Express est une simple et expressive Api au dessus des fonctionnalités web du serveur http de node.js.
Le principal job dont se charge express est de router les requêtes aux bons middlewares.
Le framework n'est pas opiniâtre, n'imposant pas de structure rigide. Il ne s'appuie pas sur le principe Convention over Configuration, étant flexible tant pour la structure que pour les middleware utilisés.
En effet un des motos d'express est @batterie not included. Ce sont l'écosystème de middleware construit autour de connect puis express qui emmènent les fonctionnalités.
- Middleware
Le concept de middleware prédate express et a été introduit dans node par connect.
Le principe est que plutot d'avoir un handleur monolytique qui se charge de gérer l'intégralité d'une requête, autant avoir une collection de handleur qui se chargent de faire un traitement spécifique.
Construire un serveur revient donc à chainer les middlewares, prétraitement, accumuler information, puis traiter les différents cas. Ainsi on a des middlewares pour gérer l'authentification, logger les requêtes, parser les cookies et corps des requêtes, régler certains headers de la réponse et ainsi de suite jusqu'à l'émission de la réponse.
Un middleware est une fonction avec trois arguments. Les deux premiers items
req
et res
sont là pour accéder aux informations de la requêtes, et pour
manipuler la réponse. Le dernier est une callback next
pour éventuellement
passer la main au prochain middleware de la chaine.
L'ordre de ces middlewares est primordial, il faut donc bien les chainer dans l'ordre qu'il convient.
En réalité il y a deux types de middleware, ceux pour gérer le cas
nominal, et ceux pour gérer les cas d'erreurs. On bascule en mode erreur dès
qu'un middleware en appelant next
avec un argument, l'erreur.
- Routage et sous applications
Comme évoqué précédement, le routage est le coeur de la fonctionnalité apporté par express.
Il s'agit d'associer à une route particulière, c'est à dire à des pattern d'url et des verbes http particulier les middlewares qui se chargeront de traiter les requêtes.
Le routage n'est pas restreint à des routes statiques, on peut avoir recours à des routes dynamiques ce qui permettra de récupérer des variables de chemin (pathvariables)
L'algorithme de routage est assez simple, pour chaque requête entrante on va suivre la chaine de middlewares, et exécuter seulement ceux dont la route va s'appareiller avec le méthode et url demandée.
A noter qu'il est aussi possible de construire des sous-applications, des routeurs que l'on viendra monter sur son serveur final.
Ceci permet ainsi d'avoir de découper son api et d'avoir des composants réutilisables, comme par exemple un module d'administration.
Express est le plus ancien des deux frameworks, d'ailleurs son développement a commencé en 2009 dès les débuts grand public de node. Une premiere milestone en 2010, beta juillet, suivie de plusieurs release candidate jusqu'en novembre avec publication v1.
Construit à l'origine sur connect
et largement inspiré par Sinatra
(Sinatra inspired web development framework, il s'est vite établis comme le
framework web de référence sur la plateforme node.
Son principal developper fut tj
, TJ Holowaychuk
travaillant à VisionMedia notament à l'origine des bibliothèques node
superagent et supertest.
Dernièrement le flambeau repris par
@dougwilson
, Tj se concentrant sur go, et
transférant le dépot à StrongLoop (à l'origine LoopBack un des framework
construit sur express)
Express est actuellent en version 4, la version 3 étant toujours maintenue.
En plus de ces deux branches actives, la version 5 est en préparation.
La principale différence entre la 3 et la 4 est que connect a été supprimé comme dépendance, et la plupart des middlewares intégrés ont été sorti dans des modules spécifiques.
Chaque passage de version majeure est documenté à la fois avec la liste des nouvelles foncitonnalités ([v3](https://github.com/strongloop/express/wiki /New-features-in-3.x), [v4](https://github.com/strongloop/express/wiki/New- features-in-4.x)) et un guide de migration (v2 vers v3, [v3 vers v4](https://github.com/strongloop/express/wiki/Migrating- from-3.x-to-4.x))
Quant à la v5, les version alphas sont déjà disponible sur npm, npm install express@5.0.0-alpha.1
, et on peut suivre leur développement sur les branches
5.x et 5.0
Celle-ci entrainera notamment la suppression de fonctions déjà dépréciées
comme res.json(obj, status)
, res.jsonp(obj, status)
, res.send(body, status)
, res.send(status)
, res.sendfile(file)
.
De part sa philosophie nodesque, batterie not includes express lui même apporte un nombre limité de fonctionnalités par lui même. Un grand nombre de middleware a été développés pour apporter celles-c et faciliter les différents aspects que peut nécessiter la bonne réalisation d'une api rest.
Les principaux sont répertoriés sur le site officiel.
Niveau communication, en plus du site vitrine, qui contient aussi tutoriel et documentation, le projet est bien entendu hébergé sur github.
Pour engager la communauté, il existe un google group dédié ainsi qu'un channel irc #express et un chat gitter.
A noter que certain framework se sont construit sur express, notamment pour libérer le développeur de nombreuses décisions via des conventions. (comme Kraken, Sails, Locomotive ou Loopback
Hapi est un rich framework for building applications and services.
hapi enables developers to focus on writing reusable application logic instead of spending time building infrastructure.
La philisophie d'hapi est assez différente d'express avec des choix que l'on pourrait qualifier de radicalement différents. En effet Hapi abstrait le serveur http node. Il introduit un cycle d'une vie de requête et propose une autre architecture pour favoriser l'isolation des différents composants de l'application et ainsi éviter les impacts inatendus.
Pour cela ses deux traits principaux de Hapi sont la configuration plutot que le code, et une organisation du code à l'aide d'une architecture de plugin
- Configuration over code
Hapi a une approche centrée configuration.
Mis à part la création des handlers de requête et leur logique métier, construire le serveur reviendra à configurer les différentes propriétés du server, les plugins chargés, et surtout les différentes routes.
La spécification de route en hapi est d'ailleurs le meilleur exemple pour illustrer cette différence d'approche avec express. Dans ce dernier, les routes sont ajoutées via différentes méthodes pour chaque verbe http, alors que dans hapi l'api offre une seule méthode qui va prendre en argument un object de configuration décrivant la route.
On retrouvera cette approche d'objet de configuration à la fois pour le serveur et les différents plugins que l'on chargera. Ceci permet notamment de changer le comportement du serveur dans les différents environnements en changeant juste la configuration. Ainsi à titre d'exemple, on pourra aisément désactiver la mise en cache en production, ne pas activer certaines surcouches comme la journalisation des requêtes pour les test. Ceci permet aussi de deployer plusieurs version de l'application, de facilement mettre en place un système de feature flipping ou de gérer l'évolution de certaines parties du serveur.
L'aspect configuration permet aussi à de nombreux préoccupation comme les performances, la robustesse et la sécurité, d'être traitées de base par le serveur tout en permettant leur paramétrage.
- Architecture de plugin et modularization
Hapi s'est construit en réaction à un des problèmes d'express, le fait qu'il ne passait pas à l'échelle d'un point de vue organisationnel. En effet, express permet de construire une api très vite, mais difficilement maintenable. Le montage des routes est un goulot d'étranglement ou il faut coordonner les changements au risque de surprises.
Du coup Hapi a été conçu pour offrir un cadre pour écrire des applications réutilisables de façon très modulaire, avec son système de plugin.
Un plugin est un composant logique, réutilisable, et indépendant apportant un certain nombre de fonctionnalités aux serveurs. Pour développer les fonctionnalités transverses, qui s'appliqueront à l'ensemble des requêtes, hapi donne un certain nombre de point d'entrée sur le traitement de la requête.
En effet plutot que de concevoir le traitement d'une requête par une suite de fonctions définies par l'utilisateur, Hapi définit un cycle de vie de la requête, avec ses points d'extensions associés, événements sur lequel on pourra accrocher des traitements comme la réception de la requête, la fin de l'authentification, le début d'émission de la réponse.
Il existe un bon nombre de plugin "natifs", développés par l'équipe derrière hapi, et reconnaissable (ou pas) à leur nom assez original.
Parmi les quasi indispensables il y a Joi pour la validation de schéma, Good pour le monitoring et logging (avec une extension pour le rejeu), aussi tv pour le debuggage intéractif, Bell pour l'authentification tierce.
Les plugins ne se réduisent pas aux fonctionnalités transverses, c'est un cadre pour découper le code, et c'est d'ailleurs la manière recommandée de structurer son application.
Hapi offre aussi un moyen d'injecter des dépendances, de mettre à disposition des objets et fonctionnalités à l'ensemble des parts du serveur.
- Une bonne synergie
Les deux approches de configuration et de plugin se nourrissent mutuellement. Ainsi Hapi offre un cadre que l'on pourra facilement étendre et configurer, cadre offrant nativement sécurité et robustesse.
De part l'aspect configuration, et le fait d'avoir des handlers en un seul bloc (en faisant abstraction des points d'extension bien défini), le routage est totalement déterministique.
Ceci est une grande force d'hapi, et surtout rend les collisions de routes détectables.
Le cas présent le serveur ne démarrera pas, et affichera une erreur explicite alors que dans express ce problème potentiel est totalement silencieux et invisible, un vrai /"Middleware hell"/.
Hapi est né bien plus récemment au Lab Walmart sous la houlette de Eran Hammer aka hueniverse. Le projet a commencé en aout 2012, né des frustrations et limitations de l'utilisation des outils existants. N'ayant pas trouvé un framework améliorable dans le sens souhaité, l'équipe est partie from scratch, reprenant quelques bonnes pratiques mises en place sur un précédent projet, sledge.
Le principal grief contre express étant le passage à l'échelle en terme organisationnel, le montage des routes étant le goulot d'entrainement où il était fréquent que des membres d'équipe se marchent les uns sur les autres en insérent de nouvelles routes.
Dans un premier temps basé sur express en proposant une abstraction au dessus, Hapo s'en est rapidement détaché. Après une série de version mineures, la première version majeure est sortie en avril 2013 faisant suite à la V0.13.
Hapi passe avec brio son baptème de feu lors du black friday 2013 à Walmart, sur lequel Eric Hammer est revenu dans quelques talks et entretient.
Une v2 est sortie en Jan 2014, suivant la v1.20 en raison de changement incompatibles pour raisons de sécurité.
S'en suit au cours de l'année 2014 plusieurs version majeures, en raison de changements non retrocompatibles, notamment en raison de l'extraction de nombreuses fonctionnalités dans des modules séparés et d'une refonte d'api.
La liste des milestones et versions majeures est disponible sur le dépot github via les issues bien catégorisées.
Ces 8 versions majeures en un lapse de temps si cours peuvent donner l'impression d'une certaine instabilité. Cependant Hapi est jeune, la SemVer pousse à vite bumper la version majeur, et surtout ces changements sont très bien documentés. D'ailleurs pour chacun d'entre eux on a le droit à une estimation du temps de mise à jour, de la complexité, des risques, et des dépendances!
La version actuelle a été un refactoring majeur du framework et de son api.
Les différentes interfaces existantes pour configurer le serveur ont été
unifiées. C'est ainsi que des méthodes comme pack
on disparu. (un pack étant
la composition de plusieurs servers en un unique objet)
L'objectif était celui de simplifier l'expérience de développement en rendant l'api plus simple et prévisible, et ainsi gagner en simplicité et apprenabilité sans perdre en puissance.
Ce gros refactor a été fait dans l'état d'esprit des /"breaking changes worth taking"/ revendiqué par le mainteneur principal.
Les 7 versions majeures en 10 mois qu'à connue l'année 2014, de la v2 à v8 semblant participer à cette refonte progressive de l'api.
La communication sur la v8 laisse à penser qu'il est peu probable d'avoir de nouvelle version majeure dans les mois qui vienne.
A noté qu'il n'y a pas de branche de développement, en public tout de moins au moins.
A titre d'example, voici l'estimation associé à la dernière version:
Upgrade time
moderate to high - a couple of days to a week for most users
Complexity
moderate - a very long list of changes, all well understood with no side effects
Risk
moderate - no side effects but a lot of changes to keep track of
Dependencies
moderate to high - every plugin must be verified to be compatible or upgraded
On retrouvera ainsi les notes de sorties des versions majeures de l'année 2014: v8 en novembre, v7 en octobre, v6 en juin, v5 en mai, v4 en avril, v3 en mars.
Bien que né a Wallmart, Hapi a su s'en détacher, et attirer une communauté conséquente autour de lui.
Néanmoins il est indéniable que son créateur garde pour l'instant un leadership certain sur la vie du framework. Cela n'a pas empêché 900 pull requests d'etre accepté, et il a 21 personnes actuellement dans l'organisation hapi gérant l'écosystème.
Le dépot initialement sur le compte de wallmartlab a été migré sur un compte
spécifique, désormais appelé hapijs
(anciennement spumko
en référence à spunco).
Celui-ci contient bien entendu le framework hapi, mais aussi l'ensemble des plugins de l'écosystème, et le code source du site contenant la documentation et divers tutoriels.
Toute la dynamique et le développement du plugin se fait via github et ses issues ce qui permet à la fois visibilité et centralisation. Les utilisateurs sont invités à y poser ici leur questions plutot que dans un forum dédié. (à l'exception bien entendu de celles ayant leur place sur stackoverflow), les changements d'api y sont documentés. Tout cela est rendu possible par un système de tag très bien pensé des issues: bug, security, dependency, discussion, documentation, release notes, breaking changes, question.
Hapi est utilisé par de nombreuses boites commerciales d'envergure listées sur leur site: notamment Disney, mozilla, Paypal, npm.
Comme Express il dispose d'un channel IRC #hapi et d'un chat gitter.
A noter aussi l'existence d'un programme de mentoring pour assister les développeurs dans leur montée en compétence sur hapi.
Après ces nombreux paragraphes un peu verbeux mais ayant le mérite de présenter la philosophie, historique, et communauté des deux frameworks, nul doute qu'un peu de chiffre permettra de se reposer les yeux.
En terme d'audience, voici quelques indicateurs qui viennent donnée une idée de l'aura des deux frameworks.
Métriques Express Hapi
Github stars
18k
4k
Github fork
3,6k
0,6k
StackOverflow
14k
180
Contributor
177
114
Github require
~360k
6k
Par contre attention, en plus de ne pas remplacer l'analyse quantitative qu'on vient de faire, ces métriques sont à prendre pour ce quelles sont. Il ne faut pas oublier de considérer que les deux frameworks n'ont pas la même ancienneté. Ce qui est bien illustré par une analyse des recherches de mots clefs sur google où on voit que bien que nouveau, hapi a bien réussi à s'implanter, et se poser en concurrent en express.
J'allais oublier, mais il y a des chances que je n'apprenne rien en disant que
tous deux on un cours dédié sur nodeschool, expressworks
et makemehapi
(et
installable depuis npm
)
Concrètement, comment on fait?
Maintenant qu'on a vu les principes sous-jacents aux deux cadriciels, leur historique et environnement, regardons comment réaliser une belle API REST.
Pour un bon rafraichissement de ces principes, une lecture de la [liste des bonnes pratiques REST est indispensable](http://blog.octo.com/designer-une- api-rest/). On se réfèrera sinon à la [refcard OCTO](http://blog.octo.com/wp- content/uploads/2014/12/RESTful-API-design-OCTO-Quick-Reference-Card- POSTER-2.4.pdf).
Avant de rentrer plus en détails, voici un très bref aperçu des deux cadriciels, un simple hello world récupéré depuis les documentations officielles respectives.
- Hello Hapi
var Hapi = require('hapi');
var server = new Hapi.Server();
server.connection({ port: 3000 });
server.route({
method: 'GET',
path: '/',
handler: function (request, reply) {
reply('Hello, world!');
}
});
server.start(function () {
console.log('Server running at:', server.info.uri);
});
On remarquera les nombreux objets de config pris en argument.
- Hello Express
var express = require('express');
var app = express();
// respond with "hello world" when a GET request is made to the homepage
app.get('/', function(req, res) {
res.send('hello world');
});
var server = app.listen(3000, function () {
var host = server.address().address;
var port = server.address().port;
console.log('Example app listening at http://%s:%s', host, port);
});
L'api qui va nous servir de use case et qu'on va utiliser pour illustrer le propos sera "Pending-Link". L'API REST va nous permettre de stocker, bookmarker de manière temporaire un lien et de le retrouver ultérieurement.
Il s'agit d'une simple api "monoressource" supportant l'ensemble des opérations CRUD et de recherche basique.
# Créer un bookmark :
curl -X POST https://api.octo.com/v1/links \
-H 'Content-Type:application/json' \
-d '{"url":"http://www.devoxx.com/"}'
< 'Location':'/v1/links/0'
# Récupérer un bookmark :
CURL -X GET https://api.octo.com/v1/links \
-h 'Accept:application/json' \
[{"url":"http://www.devoxx.com/"}]'
Pour plus de détails, vous trouverez ici la documentation RAML de l'api.
§LINK
Décrivons un peu ce que nous venons de voir dans ces deux salutations.
Avant de pouvoir servir une requête, il faut bien commencer par construire un server. (et aussi requerir la librairie, mais cela va de soi :))
Une légère différence apparaît dès ce moment là, les objets ne recoupant pas exactement les mêmes concepts.
En express l'entité principale est l'application (généralement appelée app
par convention), que l'on va configurer, agrémenter de routes ou middleware
avant d'écouter un port particulier. C'est par cette opération listen
qu'on
obtient le serveur, que l'on pourra arrêter avec la méthode close
prennant
aussi une fonction de rappel.
En hapi il n'y a qu'une seule entité, le serveur, que l'on va configurer, sur
lequel on pourra définir plusieurs connexions (avec la méthode connexion
),
qui seront instantiées lors du démarrage du serveur avec la fonction start
(qui précédera un arrêt du server avec la méthode stop
)
(à noter que dans le protoype support pending-link, le serveur express a été encapsulé pour offrir la meme interface pour avoir un main agnostic au framework utilisé)
Ces deux entités sont configurables. En hapi cela de préférence avec un objet
de config passé au constructeur new Hapi.Server();
.
En express l'application met à disposition des fonctions comme disable
ou
enable
pour activer ou désactiver un certain nombre de paramètres(§link:[htt
p://expressjs.com/api.html#app.settings.table](http://expressjs.com/api.html#a
pp.settings.table))
Une fois le serveur défini, la prochaine étape et de lui ajouter les routes, les urls que l'on veut gérer.
La différence d'approche décrite dans la première partie se retrouve au niveau de l'api.
- En express
Pour définir une route en hapi on invoquera une des méthodes get
, post
avec l'url matchée et le handleur spécifié. On a une méthode app.METHOD
pour
chaque verbe http.
Le premier argument est l'url de la route, celle-ci pouvant contenir des
variables dynamiques, un identifiant précédé par un :
. On peut aussi avoir
recours à des expression régulière
app.get('/links/:id', LinkController.get);
Il existe une méthode all
qui sera appliquée pour l'ensemble des routes,
quelque soit la méthode. Elle est utilisée pour charger les middlewares.
A noter la possibilité d'invoquer ces méthodes sur un Routeur que l'on viendra ensuite monter sur l'application. (un routeur est un middleware spécifique doté de son propre système de routage)
var router = express.Router();
router.get('/', function(req, res) {
res.send('Hellooooo');
});
// elsewhere with a different name
app.use('/some-url', router);
On préfèrera cette approche pour plus de modularité et réutilisabilité.
- En Hapi
A l'opposé, en hapi il y a une seule méthode du server
au nom peu
surprenant, route
. Celle-ci prend un objet de configuration (ou un tableau
de tels objets), devant contenir à minima les clefs method
pour le verbe
HTTP, path
pour l'url géré, et handler
pour la fonction en charge de la
gestion des requêtes.
server.route({
method: 'GET', path: '/hello', handler: function (request, reply) {
reply("Hello Links!");
}
})
On va donc choisir le verbe HTTP en le mettant comme valeur de la clé method
qui accepte l'ensemble de verbes (à l'exception d'HEAD
). Au cas où l'on
veuille définir une route agnostique de la méthode on utilisera *
, et si
l'on veut juste un sous ensemble, on passera un tableau de méthodes.
Hapi accepte aussi des variables dans les url, avec une légère différence de
syntaxe. Plutot que de les préfixer avec un :
, celles-ci sont encapsulées
dans des accolades: =path: '/links/{id}'=.
Dans les cas plus complexes on passera un objet de config
contennant les
différents paramètres. Ainsi on pourra configurer un grand nombre des aspect
comme la validation des objets, d'authentification des utilisateurs, la mise
en cache, et tout autre paramètre introduit par les différents plugin.
Notre route ressemblera ainsi à cela:
{
method: 'POST', path: '/links',
config: {
handler: LinkController.create,
validate: {payload: {url: Joi.string().required()}}
/* autres paramètres de configurations */
}
}
Pour plus de détails on se réfèrera à la documentation.
Précédemment on est vite passé sur les handler de requêtes. Regardant plus en détail ce dont il s'agit.
Dans les deux cas il s'agit d'une fonction qui prend en argument deux objets représentant respectivement la réponse et la requête. C'est ici que l'on va implanter notre logique, le traitement que l'on veut associer aux requêtes.
Comme on peut s'y attendre, le premier objet va nous permettre d'accéder à tous les détails de la requête, ses headers, ses paramètres, et plus encore.
Il n'y a guère de différences à ce propos entre les deux framewoks. (si ce n'est la manière dont les objets sont peuplés, mais on y reviendra plus tard)
Le nom n'est pas si important, mais par convention en express on va plutot
utiliser res
et req
, alors que request
et reply
sont utilisé pour
hapi.
Cette différence fait sens pour la réponse vu que reply
est une fonction
dont l'appel va déclencher le début de création de la réponse.
En express l'envoi est déclenché par l'appel de la méthode end
, send
ou un
des methodes helper similaires. (comme json()
)
Si dans hapi tout la requêtes et du début à la fin traité par le handler, en
express via le système de middleware, une requête peut passer la main. Dans ce
cas le handler aura trois argument, ce dernier étant une callback next()
pour passer la main au handler suivant.
- Exemple concret
Pour illustrer cela, voila le même handler réalisé en Hapi et en Express.
Il s'agit de la méthode pour accéder à un lien particulier. En cas de succès on renvoi une représentation json du lien, en cas d'absence ou de suppression on renverra respectivement le code 404 et 410.
var expressHandler = function (req, res) {
LinkDAO.get(req.params.id, function (link) {
if (link == null) {
res.status(404).end();
} else {
if (link.archived)
res.status(410).end();
else res.json(link)
}
});
};
var hapiHandler = function (request, reply) {
LinkDAO.get(request.params.id, function (link) {
if (link == null) {
reply().code(404);
} else {
if (link.archived)
reply().code(410);
else reply(link);
}
});
};
On notera qu'en express la méthode va finaliser le traitement de la requête alors qu'il va l'initier en hapi
La requête contient nombre de données à différents endroits. Selon les cas on aura besoin d'aller chercher les informations dans le corps, les headers, les cookies, etc.
Si les deux frameworks offrent les mêmes fonctionnalités, l'approche de collecte est assez différentes.
Pour donner accès à ces information, les serveurs font offrir des objets pour les recupérer. Ceux-ci sont stockés dans diverses propriétés de l'objet représentant la réponse dans les handler.
Ainsi en Hapi on pourra accéder à la requête via query
, entêtes via
headers
, paramètres de l'url via params
, et information d'authentification
via auth
. il est aussi possible d'avoir accès au serveur via la propriété
server
. Il s'agit d'objets, donc on pourra accéder aux différents valeurs en
utilisant au choix un accès statique (dot notation) ou dynamique (bracket
notation). Par exemple, pour récupérer l'identifiant de la ressource demandé
on utilisera request.params.id
ou request.params['id']
. Bien d'autres
existent et on pourra consulter la liste [ici](http://hapijs.com/api#request-
object).
Pour Express c'est assez similaire, avec les diverses propriétés de l'objet
req
dont path
, params
, query
et bien d'autres.
L'ensemble des objets est consultable dans la documentation.
Quant au corps, avant de l'utiliser, surtout si on veut le parser en tant que document json, ceci doit être précisé au parseur.
L'approche suivie pour peupler les representations des composants d'une requête est assez différentes entre Express et Hapi en accord avec leur philosophie. Si certaines sont fournies out of the box, pour d'autres il faut spécifier au server ce que l'on veut. Ceci est notamment le cas des cookies et du body.
En effet le parsage du corps n'est pas automatique. si on ne le précise pas on a notre beau json sous forme brute, textuelle.
Dans les deux cas on a besoin de dire au framework, que l'on souhaite parser les corps de messages.
Chez hapi on va juste configurer la route en lui précisant que l'on veut parser. Ceci ce fait via une des propriétés de l'objet de config de la route.
Pour express on intercallera en amont un middleware, bodyParser
qui se
chargera de lire le corps, de créer un objet javascript, et de l'insérer dans
l'objet request.
Une fois chose faite, on pourra accéder au corps du message comme tout objet
javascript, respectivement dans req.body
pour express, et dans
request.payload
pour hapi.
Pour certains uses cases, potentiellement plus complexes, il existe des modules dédié à l'extraction de différentes données.
On a ainsi des middleware pour parser les cookies, des modules pour supporter les tableaux et objets dans les querystring (plugin qs pour hapi), ou d'autres pour gérer les sessions.
Dans pending link, les principales données nous intéressant sont le corps de requêtes dans le cas de la mise à jour, ou création de lien, les paramètres pour filtrer les collections, et les params pour cibler une sous ressource en particulier
Plusieurs fichiers permettent de notre prototype permettent d'illustrer plus en détails.
Si pour l'accès aux paramètres il faudra dans les deux framework les méthodes "controlleur", pour la configuration on regardera respectivement les routes pour hapi, et le chargement des middleware dans le serveur pour express.
§todo: add link to code
Une fois la requête parsée, le traitement associé effectuer, il faut gérer la réponse que l'on va renvoyer à notre cher client. Et pour cela, spécifier le code de retour, les headers, ainsi que le corps de la réponse HTTP
Tout d'abord, On ne le répétera jamais assez, il est crucial que les réponses renvoient un code de retour adéquat.
On fera donc l'effort de ne pas envoyer un 200 en cas d'erreur mais un 400 Bad request, 404 not found, un petit 500 ou plus approprié.
La spécification des codes de retour est assez similaire entre express et
hapi, la principale différence étant le nom de la méthode. status
pour
exress, code
pour Hapi.
// express
res.status(404).end() // or shortcut sendStatus(404)
// hapi
reply().code(200);
Pour les deux, si non précisé par l'appel de ces méthodes la valeur par default est 200.
A noter qu'il existe en api des méthodes qui se chargeront à la fois de
peupler la réponses et le code de retour, notation avec la méthode location
qui met à la fois le code 201 Created
et le header Location
.
On pourra observer dans les contrôleurs respectif de notre prototypes les appels à ces méthodes ou nous utilisons selon les cas les codes 404, 410, 200, 201
§todo §LINK CODE
Parmi les autres incontournables des api Rest, l'utilisation des Header du protocole Http pour porter les metadonnnées de la requêtes et réponse, comme le type de contenu demandé ou envoyé, la fraicheur de la ressource disponible, et bien d'autres encore tant les utilisations sont innombrables.
Voici comment les headers sont spécifiés respectivement dans nos deux frameworks.
// express
res.set({'Content-type': 'text/plain', 'X-Custom': 'some-value'});
res.header('Lonely-Header', 'some-other-value');
res.type('json');
// hapi
reply().type('text/plain')
.header('X-Custom', 'some-value')
En pratique la plupart des headers seront gérés semi-automatiquement enrichi respectivement par des middlewares ou plugins.
Dans notre prototype ou il n'y a pas de négociation de contenu, la spécification du header apparait qu'à un seul endroit, lors de la création d'une nouvelle ressources.
Dans les deux cas on a plusieurs méthodes pour ne pas à avoir à écrire à la main le nom de l'entête.
// express création
res.location("/api/links/" + newLink._id);
// hapi creation
reply().created("/api/links/" + newLink._id);
Si dans certains cas les métadonnées suffisent, dans d'autres il est bien nécessaire d'envoyer du contenu, une représentation de ressource.
Dans la plupart des cas, on se contentera d'envoyer un objet javascript correspondant à notre resource. Dans les deux framework, celui-ci sera automatiquement sérialisé en document json.
Pour ce faire, on passera notre réponse en argument dans la callback reply
pour hapi, ou comme argument de la méthode json
pour express
Il est tout aussi possible d'envoyer du texte brut en express en utilisant
la méthode send
.
Dans les deux framework il est possible de renvoyer des fichiers déjà
constitués à la main. (méthode file
)
Cependant on préférera utiliser les middlesware ou config correspondant pour servir des fichiers ou dossier entier s'il n'y a pas de traitement spécifique à la requête.
A noter que tout deux on aussi des fonctions pour servir des templates, mais
ceci n'est pas l'objet de notre API. Si besoin est, on regardera les méthodes
reply.view()
et res.render()
Partie bonux à venir:
- Commment étendre notre serveur: middleware/plugin
- Les must have
Après cette présentation globale d'hapi et express tant d'un point de vue principe que pratique j'espère avoir donné une bonne vue d'ensemble.
Si on devait résumer, tous les deux font le job, et ils le font bien. Au niveau du code lui même les similarités sont nombreuses, cependant comme on a pu le voir les différences sont grandes en termes d'architecture, mais aussi de portée et de philosophie.
En mettant en avant la configuration, en poussant à la conception d'applications modulaires aux composants indépendants, et en détectant les collisions de routes Hapi se détache d'express dans un contexte industriel.
S'il s'agit d'exposer une api sur un site existant basé sur express, ou si on réalise une api de taille modeste ou un prototype, en solo, avec une expérience préalable d'express, dans ce cas il n'est ni nécessaire ni avisé de changer pour hapi.
Par contre si on s'apprête à faire une api d'envergure, qui est la pour durer, ou une collections de micro service, au sein d'une équipe dans ce cas Hapi est clairement plus adapté. Surtout que celui-ci permet de facilement mettre en place une stratégie de proxy pour exposer une API s'appuyant sur un backend existant, et d'ainsi migrer vers une architecture REST tout en douceur.
et puis avant d'oublier l'argument final,
because I'm hapi…