Quatrième article consacré à cette série node.js et au développement d’une petite application, celui-ci s'évertuera à vous présenter Express.js. Express est un framework de développement web, inspiré par [Sinatra](http://www.sinatrarb.com/), proposant plusieurs fonctionnalités comme un système de vues (templates) très intuitif, un robuste système de routage, un exécutable pour générer des applications et bien plus encore.

Dans la perspective de cet article et histoire de me donner du grain à moudre, j'ai travaillé une petite application mettant en oeuvre Express et l'API Rest Github. Le but, tout bête, étant de récupérer pour un repository donné tous les fichiers de type markdown (extension .md ou .markdown) et de les transmettre à l'utilisateur au format HTML. L'interface et les fonctionnalités seront très limitées, une simple liste des fichiers et la possibilité pour chacun d'eux de récupérer son contenu. Ceci pouvant constituer le point de départ d'un moteur de blog, agrégation de documentations et plus encore.

Express

Bien qu'il existe d'autre alternative à Express dans la communauté Node (liste non exhaustive), express s'impose petit à petit comme le framework web de référence avec aujourd'hui plus de 1500 watchers (githuber suivant les modifications du repository).

GitHub watched repos

Il repose sur Connect, un système à la Rack pour nous épauler en nous offrant une bibliothèque impressionnante et de grande qualité de couches middleware pour nos applications et modules Node. Il se trouve que le développeur principal d'Express est aussi un contributeur de Connect. Et si cela peut contribuer à asseoir la crédibilité de ces deux projets, une petite organisation du nom de Sencha est derrière tout ça... Vous connaissez?

Installation

Concernant l'installation, plusieurs choix s'offrent à vous: npm, curl ou git clone/submodule udpate.

Nous choisirons la technique node-boilerplate (histoire de choisir une autre méthode! Pas vraiment, il s'agit essentiellement de l'utilisation de git et support des submodules) comme décrit dans l'article précédent.

Dans la lignée de node-boilerplate, un tweet de Dion Almaer m'a fait découvrir un nouveau venu qui risque de faire parler de lui: backbone-express-mongoose-socketio. Un squelette d'application qui utilise express, socket.io, mongoose & mongodb, redis, html5 boilerplate et backbone.js.

Le serveur

Comme indiqué dans la documentation officielle d'Express, la mise en place d'un serveur est très simple et ressemble beaucoup à ce qu'on ferait avec l'API native de Node:

Configuration

Express utilisant Connect, de nombreux composants et couches middleware se trouvent à notre disposition. Certaines préoccupations communes et courantes des applis web peuvent être difficile et coûteuse à implémenter (gestion des fichiers statiques, cookie/session, gestion des erreurs, etc.). Tout ceci est géré pour nous par Connect et Express.

Example de configuration tiré de la doc Express

Si nous utilisons node-boilerplate pour générer notre application

Vous remarquerez une légère différence entre les deux versions. La première utilisant l'API d'Express pour accéder aux modules Connect, la seconde utilisant directement Connect. Le résultat étant sensiblement le même et s'explique par cette extrait du fichier index.js (celui décrivant le module Express et l'exportant):

Export des middleware Connect

La configuration d'un module aussi diffère entre ces deux extraits, le premier configurant le module methodOverride(utile dans un contexte RESTful pour le support des verbes PUT et DELETE), le second configurant le répertoire de nos vues.

Notre serveur est ainsi configuré pour servir nos fichiers statiques, gérer les routes que nous allons mettre en place, et est capable de décoder les POST de formulaire et transmettre les paramètres de requêtes à nos callback. Il s'agit d'une configuration lambda, qui répondra à la majeure partie de nos besoins.

Gestion des erreurs

Une composante essentielle de toute applications web est la gestion des erreurs, notamment la gestion des erreurs 404 et 500. Node Boilerplate est construit avec une configuration par défaut simple et robuste.

Configuration des erreurs 404/500

Pour de plus amples informations sur la gestion des erreurs dans Express, voici un lien vers le chapitre correspondant de la documentation Express.

Routage

Provenant de la documentation Express:

Express utilizes the HTTP verbs to provide a meaningful, expressive routing API. For example we may want to render a user's account for the path /user/12, this can be done by defining the route below. The values associated to the named placeholders are available as req.params.

Autrement dit, Express porte on ne peut mieux son nom! L'api fournie pour configurer nos routes est très expressive, le code se lit avec une facilité déconcertante, même pour quelqu'un n'ayant jamais touché Node ou Express.

Exemple de configuration de routes

Lorsqu'une requête est faîte au serveur, Express confronte l'URL à l'ensemble des routes configurées par nos soins, dans l'ordre de configuration et exécute tout callback défini pour la première route correspondante. Autrement dit, vous devez placer les routes de plus haute priorité en première place et affiner ensuite. Vous pouvez le voir en oeuvre avec la configuration de la route 404 (app.get('/*')).

Vous pouvez passer le contrôle à la prochaine route correspondante en appelant le troisième argument, la fonction next(). Quand plusieurs routes correspondent au même chemin, les contrôleurs seront alors exécutes en ordre jusqu'à ce qu'un callback ne fasse pas d'appel à next(). Cette méthode est aussi utile dans la gestion de nos erreurs où l'on sera amené à utiliser quelque chose comme: next(new Error('an hopefully meaningful error message')).

Les vues

Une fois que nous avons correctement mis en place nos routes, il reste à fournir à l'utilisateur une réponse. C'est quelque chose que nous avons déjà fait dans les extraits de code précédents sans toutefois nous y attarder (res.send() et res.render())

Express supporte de nombreux systèmes de templates différents:

Le nom des vues prennent la forme de vue.ENGINE, où ENGINE correspond au nom du module qui sera "required" (fait d'importer la dépendance dans Node). Par exemple, utiliser view.ejs importera le module ejs via require('ejs'). L'utilisation des vues se fait par l'entremise de res.render() (qu'on a pu voir en oeuvre dans le chapitre de gestion des erreurs).

Exemple utilisant haml pour retourner index.haml

Express tentera alors de rendre notre vue index.haml contenue dans le répertoire /views (que l'on a configuré plus tôt via server.set('views', __dirname + '/views');). Par défaut, express passera le contenu d'index.haml en tant que variable locale body à layout.haml. Nous pouvons toujours l'empêcher en utlisant l'option layout: false lors de l'appel à res.render().

Express propose de nombreuses possibilités au niveau des vues et est très flexible. La documentation correspondante vous en apprendra davantage, notamment sur le support des vues partielles (partial() depuis une vue).

Github API

Github fournit une API REST complète permettant de récupérer informations et contenus des repository Github. L'API propose également un mécanisme d'authentification et permet d'effectuer des opérations de mise à jour comme suivre ou arrêter le suivi d'un repo/user, d'ouvrir/fermer un bug (Github issue), de mettre à jour nos informations utilisateur(mail, nom, site, ...) ou celle d'un repository nous appartenant, etc. L'API permet également d'utiliser une fonction de callback en ajoutant simplement le paramètre ?callback=js à n'importe quel appel, le résultat JSON sera alors encapsulé dans cette fonction, de manière à pouvoir l'exécuter et permettre la mise en place de requêtes cross-domain (JSONP).

Dans le cadre de cette article et la conception de notre client Github, nous n'utiliserons que l'API Repo et Object.

node-github

Différentes librairies sont disponibles pour accéder à l'API Github, et ce dans plusieurs langages (Java, Python, Ruby, ...). Le contexte de notre exercice se trouvant être Node, ce sont les implémentations javascript qui nous intéressent le plus, et notamment le module node-github qui propose une API asyncrhone et orientée objet.

node-markdown

Un peu hors du contexte de l'API Github, mais nous en aurons besoin plus tard. Nous utiliserons donc également le module node-markdown pour formater nos données depuis le format markdown vers le format HTML. Nous aurions pu également effectuer ce traitement coté client par l'utilisation de showdown sur lequel le module node est basé.

Prise en main rapide

Ce bout de code permet de récupérer le contenu d'un fichier spécifique sur un repository, et de le retourner au format HTML que nous affichons simplement dans la console node. Ici, nous disposons du nom de l'utilisateur github, du repo concerné, du sha du tree associé (qu'on peut trouver à coté de chaque commit) et le chemin du fichier. Bon nombre de requête requièrent ce paramètre sha, mais il en existe quelques unes permettant de les récupérer via l'API Github (la liste des blobs notamment).

Pour se faire la main, nous pouvons également utiliser node en ligne de commande, importer le module github et commencer à jouer avec son API, voici un exemple d'utilisation de l'API Repo pour récupérer la liste des repositories d'un utilisateur (ici, moi-même).

node command line

Conception du module githuber

Dans le contexte de l'exercice décrit ici, nous n'aurons besoin que de deux méthode de l'API du module github: listBlobs et getRawData.

Le premier appel permet de récupérer la liste complète des fichiers du repo avec notamment le sha associé à chacun des fichiers, le deuxième permettant de récupérer le contenu brut du fichier (peu importe le format demandé yaml, json etc.).

Lorsque j'ai commencé à implémenter directement tout ça, je me suis vite rendue compte que le fichier server.js (notre fichier principal décrivant la configuration de notre serveur, route, erreur etc.) allait vite devenir illisible. Il est donc de bon ton d'utiliser le mécanisme de module de Node, reposant sur la convention commonJS, pour hiérarchiser et structurer un peu notre code, sans parler de découplage des responsabilités.

Voyez ce module comme un service (que j'ai bien failli appeller githubService...), le composant logiciel fournissant l'essentiel du traitement des données reçues par github (filtre, format des données, etc.). Ceci correspondra à notre "logique métier" (bien que la plupart de cette logique se trouve du coté de Github qui fournit le service et dont nous sommes consommateurs).

Code du module arbitrairement appelé “githuber”

On voit bien la nature asynchrone de node qui pourra en rebuter certains au premier abord. Il s'agit effectivement d'un paradigme complètement différent de ce à quoi nous sommes habitués, il est très important de savoir quand nous nous situons dans un contexte asynchrone où l'on doit utiliser le passage de callback (présent dans la signature des deux méthodes publiques fournises) ou l'utlisation des évenements Node (demystifying-events-in-node).

Décrivons maintenant rapidement les méthodes listBlobs et getArticle qui seront utilisées depuis nos contrôleurs (les callbacks configurés pour chaque route de notre application).

githuber.listBlobs(cb, next)

Cette méthode permet de récupérer la liste complète des fichiers d'un repository donné (celui décrit dans le module config).

Seuls les fichiers de type markdown nous intéressent. Le service github nous retournant l'ensemble des fichiers du repo, la méthode effectue un petit test sur chacun d'entre eux grâce à l'expression régulière /([\w|-]+).(md|markdown)/ permettant par la même occasion de déterminer le titre du fichier (qui sera affiché à l'utilisateur).

Exemple d'utilisation

Ici encore, nous retrouvons toute l'asynchronicité de Node, la fonction passé à listBlobs sera alors invoqué dès que nous disposerons de résultat à consommer. Nous récupérons également les informations du repo (un simple getter sur la variable contenant ces infos). Nous passons alors à la vue différentes valeurs dont data(disponible en paramètre de ce callback), correspondant à notre liste de blobs.

La vue corresponante list.ejs utilise une vue partielle pour afficher tout ceci à l'utilisateur. Les vues partielles sont une manière simple et efficace de "boucler" dans vos vues. Au lieu d'utiliser une structure itérative, vous pouvez laisser Express gérer l'itération pour vous. Pour chaque élément de la collection, la vue item.ejs est utilisée avec une variable locale (item ici) référençant l'élément en cours d'itération.

Utilisation dans les vues

githuber.getArticle(sha, next, cb)

Cette méthode permet de récupérer le contenu d'un article donné. Il s'agit du contenu brut (text/plain) et ce quelque soit le format spécifié au niveau de la requête github (yaml, json, etc.). Ce contenu est alors transmis au callback passé en paramètre aprés avoir été traité par le module markdown, permettant la conversion vers le format HTML.

Exemple d'utilisation

Vue associée

Remarquez l'utilisation de l'opérateur <%- %> au lieu de <%= %> pour empêcher l'échappement de notre string HTML.

Conclusion, conclusion

Nous arrivons alors à la fin de cette article. Nous avons présenté et pris en main les principales fonctionnalités d'Express, appréhendé et utilisé l'API Github Object pour finir sur la conception de notre propre module.

Vous pourrez retrouver l'ensemble du code relatif à cette article sur ce repository github: github.com/MkLabs/idonthaveanameyet

Le nom du projet n'est vraiment pas très originale et j'avoue avoir manquer d'inspiration sur le coup. Je n'avais pas d'idée de nom, le projet s'est alors appelé de lui même idonthaveanameyet...

Dont voici un petit screenshot: Demo

L'interface très épuré reprends la structure et le style de la nouvelle interface twitter (sans bien sûr aller aussi loin). Une simple liste accessible depuis l'accueil permet de cliquer sur chacun des fichiers markdown du repo afin d'afficher leur contenu. Ici, l'exemple porte sur le repo npm qui dispose de l'ensemble de la documentation sous ce format.

Pour ceux désirant jouer un peu avec cette appli et changer les informations du repository utilisé, il vous suffit de suivre les quelques étapes suivantes:

Le fichier config.js vous permet de facilement modifier les informations du repository utilisé (name, repo, branch)

Vous pouvez alors lancer votre navigateur favori et tester http://localhost:15001.

Mais comme je suis un brave gars, je vous ai mis en place une version en ligne disponible via http://blogit.mklog.fr, le repository configuré étant Node bien entendu ;).

Ressource pour aller plus loin

Connect