Marmelab Javascript Boilerplate: deux jours de gagnés à chaque démarrage projet

Kévin MaschtalerGildas Garcia
#js#oss

Nos projets utilisent pour la plupart JavaScript du front au back - ce qu'on appelle des projets JavaScript "full-stack". Nous démarrons la plupart de nos projets à partir d'un même modèle, un boilerplate. Il cristallise nos choix techniques (Reacts.js, Koa.js, ng-admin, etc) et bonnes pratiques. Il permet surtout de gagner plusieurs jours à chaque démarrage. Nous avons choisi de le documenter et de le partager avec le plus grand nombre.

Pourquoi un nouveau boilerplate JavaScript ?

Les projets sont courts chez marmelab - quelques mois en général. C'est pourquoi nous démarrons de nouveaux projets très souvent. Mais démarrer un nouveau projet en JavaScript full-stack est douloureux. En témoignent les nombreux articles sur le sujet.

Npm compte à ce jour 247 985 packages disponibles. A titre de comparaison, Packagist (composer - PHP) en héberge 88 053 packages, et PyPI (pip - Python) seulement 75 825. Autant dire qu'il y a de quoi s'y perdre, même pour des développeurs confirmés qui pratiquent une veille constante !

Chez marmelab, nous avons essayé de très nombreuses librairies JavaScript. Nous en avons adopté une petite partie, que nous avons ensuite éprouvées sur la majorité de nos projets. Les architectures de ces projets se ressemblent finalement toutes, à peu de choses près. Afin de gagner deux à trois jours d'initialisation à chaque projet, il est vite devenu nécessaire d'industrialiser nos bonnes pratiques.

Nous n'avons pas trouvé chaussure à notre pied dans la pléthore de boilerplate et autre générateurs que propose la communauté JavaScript. Qu'ils soient couplés à MongoDB (alors que nous utilisons majoritairement des bases relationelles), qu'ils proposent une arborescence qui devient illisible après quelques jours de développement, qu'ils négligent l'outillage nécessaire à la production (script de déploiement, API de healthcheck, authentification, etc), ou qu'ils ne traitent qu'une partie du problème (le front pour la plupart, mais pas le back), aucun ne cadre suffisamment avec nos pratiques. Nous avons donc décidé de créer notre propre boilerplate, à partir de nos projets récents.

Et puisqu'à marmelab nous aimons bien l'open source, il est évidemment ouvert au public !

Mais avant de vous plonger dans le code, voici une petite présentation.

Architecture

Tous les choix techniques sont le fruit d'un consensus entre tous les développeurs de marmelab. S'ils ne correspondent pas à vos besoins, vous y trouverez peut-être une approche intéressante.

Le boilerplate contient 3 applications: deux applications clientes, et une application serveur. Côté client, deux applications "Single-Page Applications" (SPA) discutent avec des APIs. Une application pour les besoins des clients finaux (frontend), et une seconde pour les besoins d'administration (admin). Seul du JSON transite entre les applications clientes et le serveur. Les pages web supportant les applications clientes sont statiques, et peuvent donc être hébergées sur un CDN. Quant au code serveur (api), il est autant que possible indépendant du service qui l'expose ; le gros du code réside dans le "domain model" qui est du code JavaScript pur, sans framework.

Nous n'avons pas (encore) intégré dans ce boilerplate d'application web mobile (via Cordova), universelle (via le rendering React côté serveur) ou native (via React Native). Notre expérience nous montre que pour ces applications-là, le boilerplate nécessaire est sensiblement différent. Ce sera peut-être le sujet d'un prochain billet de blog.

Langage

JavaScript est devenu un nouveau langage (et bien meilleur) dans ses dernières itérations. Nous avons adopté ES6/ES2015 depuis de nombreux mois sur tous nos projets, pour le front comme pour le back. Mais comme les navigateurs web ne sont pas encore compatibles avec ce standard, cela implique pas mal de choses :

Intégrer et configurer ces outils est long, fastidieux, et demande parfois de se battre contre une documentation pauvre, voire inexistante (Webpack). Le boilerplate permet de partir d'un projet pré-configuré, sur lequel nous avons déjà résolu plein de petits problèmes et poussé ces outils au maximum.

Structure de fichiers

.
├── bin                     # Binaries
├── build                   # Builded applications
├── config                  # Configuration files
├── doc                     # Documentation
├── e2e                     # Functional tests
├── src                     # Applications directory
│   ├── admin
│   ├── api
│   └── frontend
└── webpack                 # Webpack configuration

Notre architecture est assez simple. Les dossiers src, build, webpack et e2e contiennent chacun un sous-répertoire par application, et à l'intérieur de ces applications le code est découpé selon la logique métier. Cette structure est assez légère pour ne pas ralentir le développement en début de projet, et assez discriminante pour rester pertinente même après plusieurs années.homme de développement.

Vous noterez d'ailleurs l'absence de dossier pour les tests unitaires: ils sont situés dans le même répertoire que le code testé. Par exemple, ProductItem.spec.js est le test unitaire du composant ProductItem.js.

Le boilerplate contient une démo non triviale, basée sur un modèle de données inspiré du e-commerce (utilisateurs, produits, commandes). Il montre comment organiser le code au mieux. Vous pouvez supprimer ce code dès que vous commencez à implémenter votre propre domaine.

API

L'application centrale, l'API, est construite avec la dernière version de Node.js (V5) et le framework Koa.js. Elle utilise une base de donnée PostgreSQL pour sa persistence, s'y interface grâce à un générateur de requête SQL fait maison, et un helper pour construire des endpoints d'API facilement (crud.js). Pas d'ORM, des requêtes écrites en SQL, et une performance au top. L'emploi de db-migrate permet de gérer les migrations de modèle de données.

Son rôle se borne à exposer les tables de la base de données en REST, et à gérer les autorisations d'accès, le rate limiting, etc... (ce qu'on trouve habituellement dans un API Gateway). On y met le moins d'intelligence possible. Sur certains projets, nous remplaçons d'ailleurs cette API Node.js par PostgREST, qui nous apporte toute satisfaction.

La sécurité de l'application d'API respecte les standards de l'industrie:

Pour les tests, nous utilisons Mocha et Request. Enfin, l'API comporte quelques outils pratiques comme deux logger basés sur Winston (l'un pour les requêtes HTTP, l'autre pour l'applicatif), et une vue healthcheck chargée de rendre compte de l'état de l'API à tout instant.

Frontend

React, Redux

Nous avons choisi React et l'architecture Flux qui nous permettent d'appréhender facilement notre projet, quelle que soit sa taille ou sa complexité. Parmi les nombreuses implémentations de Flux, nous avons choisi Redux et son écosystème. Le débuggage en front est facilité par l'utilisation de redux devtools. Enfin, afin de faciliter l'écriture et les tests des interactions avec l'API, nous avons opté pour Redux Saga.

Le code structurant l'application est hébergé dans le répertoire app. On y trouve la création des reducers, des routes, des sagas ainsi que l'initialisation du store et de ses middlewares.

Chaque domaine est ensuite structuré de la même manière et expose des routes, des actions, un reducer, une saga et des composants React (smart et dumb).

Chaque fichier ou presque est accompagné de son pendant pour les tests unitaires.

Si vous ne connaissez pas encore Redux, nous vous recommandons vivement de suivre les vidéos de Dan Abramov sur egghead.io. Concernant redux-saga, les articles suivants sont une excellente introduction:

Design

Pour un démarrage rapide, nous avons opté pour la version la plus récente de Bootstrap, bootstrap 4. Nous l'utilisons dans sa déclinaison SASS (via node-sass).

Ceci nous permet de démarrer avec un style "bien mais pas top", mais surtout avant de disposer de maquettes, qui viennent souvent assez tard dans nos projet. Lorsqu'il faut intégrer le graphisme, nous pouvons modifier les variables de la configuration Bootstrap ou inclure notre propre code SASS dans le fichier main.scss.

Console d'administration

Si vous avez déjà utilisé un framework avec un générateur d'administration (comme Sonata ou Django Admin), vous savez combien de temps cela fait gagner. Choisir une architecture API-centric ne doit pas nous empêcher de profiter d'un générateur d'administration. C'est pourquoi marmelab a conçu ng-admin, projet open-source déjà très populaire. C'est un framework frontend (basé sur Angular.js) permettant d'afficher une console d'administration basée sur une API REST, via une simple configuration.

Notre boilerplate utilise évidemment ng-admin : l'application ./src/admin y est entièrement consacrée, et montre un exemple de structuration de code.

Vous pouvez trouver bizarre qu'un projet contienne à la fois une application en React et une application en Angular. La raison est simple: a date, il n'existe aucun équivalent de ng-admin dans le monde React. Nous avons bien commencé à nous atteler à cette tâche, mais l'écosystème React est encore mouvant, et il reste des mois de développement avant d'arriver à une parité fonctionnelle avec ng-admin.

Tests

Nous développons par itérations, donc la non-régression d'une itération à une autre est un besoin impérieux. Nous cultivons également la propriété collective du code. Enfin, nous demandons à nos clients d'inclure des tests d'acceptance dans leurs User Stories, pour pouvoir automatiser la recette. Le boilerplate contient donc des tests unitaires et fonctionnels sur tous ses composants.

Sur l'application Frontend, les tests unitaires sont réalisés avec Mocha et Enzyme. Enzyme nous permet de tester très facilement le rendu et le comportement de nos composants React.

Sur l'API, nous utilisons également Mocha pour les tests unitaires, avec Sinon.js pour le stubbing et Chai pour les assertions.

Enfin, les tests fonctionnels ("e2e" pour "end to end") sont réalisés via Selenium. Afin d'en faciliter l'utilisation, nous avons opté pour Nightwatch.

L'intégration avec Travis CI est déjà faite, ce qui n'est pas une mince affaire. Cela permet de se lancer illico dans le développement avec la couverture d'une Intégration Continue.

Automatisation

Le travail de développement est rythmé par le cycle edition de code / vérification du résultat. Pour accélérer ces cycles et les rendre moins pénibles, nous avons automatisé beaucoup de choses, à travers:

  • Un makefile auto-documenté qui s'occupe de l'installation, du build, du lancement des tests, et du déploiement en une seule commande à chaque fois.

  • pm2 pour démarrer, scaler et redémarrer le serveur Node.js à chaque changement de code.

  • Du hot reloading partout, afin de nous épargner de précieuses secondes chaque jour. Pour cela, nous utilisons webpack-dev-server et les options de pm2.

  • Concernant le déploiement, nous avons l'habitude de déployer notre API sur un serveur qui fait tourner Supervisor, et les applications frontend sur un Bucket S3. Toutes les configurations sont pré-installées dans le projet, et Fabric nous aide gérer le déploiement sur toutes les infrastructures.

Asciinema might track you and we would rather have your consent before loading this.

Always allow

Batteries included but removable

Au contraire d'un framework full-stack, ce boilerplate est constitué d'une myriade de librairies indépendantes. Nous avons choisi le meilleur compromis selon notre expérience entre rapidité de développement, maintenabilité, robustesse, et performance. Si l'un des choix de librairies ne vous convient pas, remplacez-la par une autre, le boilerplate continuera à fonctionner.

Pour coller au plus près d'un cas d'utilisation concret et pouvoir proposer un boilerplate exhaustif, le repository contient un lot de fonctionnalités assez conséquent.

Vous pouvez le tester en clonant le dépôt marmelab/javascript-boilerplate sur GitHub.

Conclusion

Ce boilerplate est suffisant pour nos besoins ; il nous fait gagner plusieurs jours à chaque démarrage, et représente donc une économie substantielle. Il permet aussi aux nouveaux arrivants de bénéficier de la R&D et du savoir-faire des anciens. Cependant, il se base sur un ensemble de choix d'architecture et d'outils jeunes. Il va donc continuer à vivre et à évoluer, lorsque nous découvrons de meilleurs compromis.

Ce boilerplate est plutôt destiné à des développeurs avertis, et habitués aux technologies employées. Si vous avez des suggestions, des questions ou que vous rencontrez des difficultés à l'utiliser, n'hésitez pas à ouvrir une issue !

Et si d'aventure une équipe talentueuse résussissait à créer un framework à la philosophie similaire, et à rassembler autour de ce projet la majorité des développeurs de la communauté JavaScript, nous serions ravis de rejoindre ce navire. Difficile à l'heure actuelle de prédire qui prendra cette place (peut-être MeteorJs ?).

Did you like this article? Share it!