Node.js & Express: comment packager et partager ses applications

S’il y a bien une chose qui craint avec les technologies « cool » et récentes, c’est la doc. Et Node.JS (ou plutôt la grande majorité de ses modules, car l’API de Node elle-même est plutôt bien documentée) n’échappe pas à la règle, et les contributions de @visionmedia quoi que d’excellentes facture et très populaires, en sont hélas une illustration ;) Du coup, il y a des fonctionnalités qu’on ne découvre qu’en farfouillant dans le code…

Je refléchissais en ce moment à un moyen de « packager » les applications Express dans Node.JS, de manière à les réutiliser. Naïvement, j’étais persuadé que ça n’était pas possible, et je suis parti dans le développement d’un framework permettant de simplifier ce packaging, avec des vues embarquées, etc… Naïf, oui, car évidemment cette fonctionnalité existe déjà dans Connect (la couche sous Express): quand on appelle app.use(une_autre_app), c’est comme si on « fusionnait » les deux applications. Ce comportement, ainsi que les valeurs de configuration disponibles, étant très mal documentées, je vais vous en parler ici :)

L’application à partager

Supposons que j’ai une application « forum », avec la structure habituelle:

+- app.js
+- package.json
+- views/
   +- layout.ejs
   +- index.ejs
   +- thread.ejs

En substance, l’application est de cette forme:

var app = require('express').createServer().configure(function() {
  // ... Configuration
 
  this.get('/', /* liste des threads */)
  this.get('/threads', /* idem */)
  this.get('/thread/:id', /* affichage d'un thread */)
})
 
app.listen(8080)

Le code est disponible en intégralité sur gist.github.com (pour alléger un peu l’article). Une démonstration (gracieusement hébergée par DotCloud, dont il faudra que je vous parle un jour :)) est également disponible en ligne.

Un simple npm install pour télécharger les dépendances, un petit node app et c’est parti, localhost:8080 vous affichera la même chose que demoexpress1.naholyr.dotcloud.com.

Préparer l’application pour être réutilisable

Dans un premier temps, il faut que je transforme mon application en module pouvant être importé dans un autre script. En l’état, si j’appelle require('./app'), le serveur démarre directement. Il faut donc que l’appel à listen() dépende de la façon dont on fait appel à l’application.

Je vais donc dans un premier temps rendre mon application « exportable » (un require('./app') retournera l’application elle-même):

 /** Déclaration de l'application */
-var app = require('express').createServer().configure(function() {
+var app = module.exports = require('express').createServer().configure(function() {

En plus de ça, il ne faut que l’application ne démarre directement que dans le cas où le module est exécuté directement par node. L’objet require permet de détecter ce type de différence:

 /** On démarre le serveur sur le port 8080 */
-app.listen(8080)
+if (require.main === module) {
+  app.listen(8080)
+}

Bien sûr une autre méthode aurait été d’avoir deux script: un app.js qui ne fait que déclarer l’application, et un server.js qui l’inclut et appelle sa méthode listen(). Bref, ça marche.

Réutiliser l’application

Maintenant, supposons qu’on a une toute autre application, qui d’ailleurs utilise jade, et dans laquelle on souhaiterait réutiliser notre forum, avec des URLs préfixées par « /forum/… ».

Dans un premier temps on va prendre notre première application et la poser dans un dossier lib/forum par exemple, puis simplement dire à notre application de l’embarquer à l’aide de use():

var express = require('express')
var app = express.createServer()
 
// ... ici toute la configuration de mon application ... //
 
var forumApp = require('./lib/forum')
app.use('/forum', forumApp)
 
app.listen(8080)

Je démarre mon application, je vais sur http://localhost:8080 (OK, ça affiche mon index) puis http://localhost:8080/forum et là paf! Je vois mon forum :) Bon, par contre:

  • Je n’ai pas le layout de mon application actuelle, mais seulement celui du forum
  • Je n’ai pas non plus le titre de mon application principale…
  • Mes liens ne marchent pas, en effet /threads n’est pas reconnu, c’est normal il faudrait plutôt que ce soit /foru/ threads vu qu’on a « monté » l’application sur /forum

Pour tout ça, il suffit de jouer sur le « view options« . Dans un premier temps on va déjà corriger les liens, en rendant configurable via une variable de template dans mon application « forum » la racine des URLs:

@@ lib/forum/app.js
    this.set('view options', {
-    title: 'Forum'
+    title: 'Forum',
+    rootUrl: '/'
 
@@ lib/forum/views/index.js
-   <li><a href="/thread/<%= threads[i].id %>">Thread #< %= threads[i].id %>
+  <li><a href="<%= rootUrl %>thread/< %= threads[i].id %>">Thread #< %= threads[i].id %>
 
@@ lib/forum/views/thread.js
- <p><a href="/threads">Back to index</a></p>
+<p><a href="<%= rootUrl %>threads">Back to index</a></p> 
</a></li></a></li>

Je n’ai plus qu’à, dans mon application principale, reconfigurer l’application intégrée pour qu’elle corresponde mieux à mes besoins:

// ...
 
var forumApp = require('./lib/forum')
forumApp.set('view options', {
  title:   app.set('view options').title,
  layout:  app.set('views') + '/layout.' + app.set('view engine'),
  rootUrl: '/forum'
})
 
// ...

On a ainsi:

  • spécifié qu’on utilisait le même titre global que l’application en cours (parce que par convention, tant qu’à faire, on avait aussi défini une variable « title« )
  • indiqué le chemin complet du layout de notre application, afin que l’app embarquée n’essaie pas d’utiliser le sien à chaque appel
  • spécifié la « rootUrl » afin de modifier les liens

Retour sur http://localhost:8080/forum/threads bingo, on a bien notre style, et nos liens fonctionnent :)

Là encore, le code est à votre disposition sur gist.github.com et une démo est présente sur demoexpress2.naholyr.dotcloud.com.

Conclusion

Même si ça manque de documentation, et de standards, tout est là pour que ça fonctionne :)

Pour que votre application puisse être réutilisée dans d’autres applications Express, il faut bien la développer de manière à ce que les URLs soient configurables (au moins la racine). Je conseillerais de passer par un helper dédié à la génération de vos URLs, ça simplifierait fortement ce travail. Je n’ai sans doute pas encore exploré tous les problèmes qui peuvent survenir, comme la réutilisation des partials… Quand j’aurai fait le tour de la question, je publierai un nouvel article avec cette fois-ci des conventions précises, ainsi que quelques modules permettant de simplifier ce travail (notamment la génération des URLs).

5 réflexions au sujet de « Node.js & Express: comment packager et partager ses applications »

  1. Popy

    Donc, ça roxx NodeJs ? C’est de la glandouille perso ou tu as réussi à vendre ça ?

    (arf, la version modbile de ce blog est gerbante)

    Répondre
    1. naholyr Auteur de l’article

      C’EST PAS DE LA GLANDOUILLE, C’EST DE LA VEILLE TECHNO !

      Sinon à part ça, oui ça commence à se vendre. La techno est très jeune (2009 première release) et les modules autour sont pour l’essentiel de 2010 ou 2011, donc plutôt très récents aussi. Donc en gros, ça ne peut être utilisé que pour des petits projets pour l’instant.

      De ce que j’ai vu : knpLabs a 2-3 projets client avec Node (inconnus pour l’instant, même si je fais de mon mieux pour faire de l’espionnage industriel), et il y a quand-même quelques applications en production :) Chez Clever Age, non, pour l’instant je n’ai pas vu passer de projet client.
      Mais clairement, c’est complètement auto-centré pour l’instant: c’est plus des applis en node, par node, pour node.

      Sinon en prod j’ai 301.tl quand-même :P

      P.S: la version mobile c’est le truc par défaut de wordpress, ça fait son taf ^^

      Répondre
  2. BONNAURE Olivier

    Bonjour,

    Nous avons utilisé node.js pour un jeu de coinche temps réel (avec Socket.io)
    http://defycards.com

    Cela est maintenant très stable …

    Nous utilisons node.js pour d’autres projets (en développement) mais en général nous travaillons avec Rails … Cela dépend des besoins …

    Sinon l’article est intéressant :) Merci de partager ces infos.

    Répondre
  3. popy

    Sisi, c’est de la glandouille. J’imagine bien que ça c’est deja vendu dans le monde (apres tout certains vendent même du ruby), mais la vrai question était « ca se fait dans le coin ? »
    Par contre, gros bonhomme. Ta veille techno la plus aboutie c’est l’url shortener, gros travail sur la charte (quand même pas toi qui l’a pondue ?)

    Me tarde d’avoir un projet de cet acabit en tout cas, la les seuls trucs un peu bandants que je fait c’est des tweaks de typo…

    Répondre

Laisser un commentaire