# ProFeel — un serveur stub Swagger pour avancer sans API > Retour sur ProFeel, une app Ionic de profilage professionnel. L'API tierce n'existait qu'en production. On a monté un stub server Swagger en Node/Express pour développer sans attendre, et ça nous a fait gagner un temps fou. Date : 12/06/2017 Auteur : Aurélien N. Tags : Ionic, Node.js, Swagger, API, Webpack --- ProFeel est une app de profilage professionnel. Le principe : l'utilisateur répond à une série de questionnaires comportementaux (mises en situation managériale, choix de mots, hiérarchisation de valeurs) et l'algorithme du client déduit un profil de personnalité. L'app couvre trois "univers" : Job (recrutement), Friends (affinités), Love (compatibilité). Chaque univers est un parcours de 18 questions avec un timer. Le projet est classique côté technique : une app Ionic 1, du Webpack, du Babel, du SCSS. Ce qui est moins classique, c'est la situation dans laquelle on s'est retrouvés avec l'API. ## Le problème : une API qui n'existe qu'en prod Le client avait un algorithme de profilage qui tournait déjà en production, derrière une API REST. C'est cette API qui servait les questionnaires, collectait les réponses et calculait les profils. Notre boulot : construire l'app mobile qui consomme cette API. Sauf que le client n'avait pas d'environnement de test. Pas de staging, pas de sandbox, rien. L'API tournait en production, point. Et il nous promettait un accès "dans quelques semaines". En attendant, on avait la doc Swagger de l'API et c'est tout. On avait deux options. Attendre l'accès et perdre trois semaines. Ou trouver un moyen de bosser sans. ## Le stub server : 50 lignes de Node pour débloquer le projet L'idée était simple : si on a la spec Swagger, on peut monter un faux serveur qui répond exactement comme le vrai. Pas un mock fragile avec des `if/else` dans l'app, un vrai serveur HTTP séparé, avec les bons endpoints, les bons formats de réponse, et des données réalistes. On a utilisé `swagger-express-mw`, un middleware Express qui lit un fichier `swagger.yaml` et route automatiquement les requêtes vers des controllers JavaScript. Le routage est déclaratif : chaque endpoint dans le Swagger pointe vers une fonction via `operationId`, et le middleware s'occupe de valider les paramètres et d'appeler le bon controller. ```javascript // app.js — le serveur complet tient en 35 lignes const express = require("express"); const SwaggerExpress = require("swagger-express-mw"); const logger = require("morgan"); const app = express(); app.use(logger("short")); // Swagger UI à la racine pour la doc app.get("/", function(req, res) ); SwaggerExpress.create(, function(err, swaggerExpress) { if (err) swaggerExpress.register(app); app.listen(process.env.PORT || 10010); }); ``` Les controllers sont tout aussi minimalistes. Un fichier JSON contient les 18 questions du questionnaire avec leurs réponses possibles, rédigées en français, dans le format exact de l'API de production. Les controllers lisent ce JSON et renvoient les données : ```javascript // surveys.js — renvoie un questionnaire complet const = require("./stub_data.json"); function getSurveyById(req, res) { const surveyId = req.swagger.params.surveyId.value; res.json({ idSurvey: surveyId, numQuestions: questions.length, questions: questions.map((q) => Object.assign(, q, )), }); } ``` ```javascript // questions.js — GET et PATCH sur une question function getQuestionForSurveyByPosition(req, res) { const surveyId = req.swagger.params.surveyId.value; const questionPosition = req.swagger.params.questionPosition.value; if (questionPosition < 0 || questionPosition >= questions.length) { res.status(404).json(); } else { const question = questions[questionPosition]; res.json(Object.assign(, question, )); } } function updateQuestion(req, res) { const questionPosition = req.swagger.params.questionPosition.value; const update = req.swagger.params.body.value; Object.assign(questions[questionPosition], update); res.json(Object.assign(, questions[questionPosition], )); } ``` Le PATCH modifie les données en mémoire. C'est volontaire : ça permet de tester le parcours complet (répondre à une question, voir la réponse persistée, revenir en arrière) sans base de données. Au redémarrage du serveur, tout est remis à zéro. ### Pourquoi Swagger et pas un mock codé en dur On aurait pu coller un fichier JSON derrière un `json-server` et basta. Mais le Swagger apporte quelque chose en plus : la validation automatique. Le middleware vérifie que les requêtes de l'app respectent le contrat, bons paramètres, bons types, bon format de body. Quand on s'est trompés sur un champ, on l'a su tout de suite, pas trois semaines plus tard en branchant la vraie API. Et le stub embarque Swagger UI à la racine. N'importe qui dans l'équipe, y compris le client, peut ouvrir le navigateur, voir les endpoints disponibles, et tester les requêtes sans lire le code. ### Le déploiement : Heroku en 30 secondes Le stub est déployé sur Heroku. L'app Ionic pointe dessus en production via une variable Webpack : ```javascript // webpack.config/development.js new webpack.DefinePlugin({ 'process.env': { 'HOST_URL': JSON.stringify(`http://$:3000`), } }), // webpack.config/production.js new webpack.DefinePlugin({ 'process.env': }), ``` En dev, l'app tape sur le serveur local. En "production" (sur les devices de test), elle tape sur le stub Heroku. Le jour où le client a fourni l'accès à la vraie API, on a changé l'URL. Rien d'autre. ## L'app Ionic : un projet classique bien outillé Côté app, c'est de l'Ionic 1 avec Angular 1.x. Ionic 2 est sorti, mais on reste sur la v1 pour ce projet : le scope est limité, l'équipe connaît le stack, et Ionic 1 fait le boulot. L'originalité est plutôt dans le tooling. On a monté un pipeline Webpack 2 complet avec Babel 6, ce qui nous permet d'écrire du ES2015+ (classes, arrow functions, destructuring, modules) tout en ciblant Cordova. C'est moins courant que ça en a l'air : la plupart des projets Ionic 1 qu'on croise utilisent encore Gulp ou le CLI Ionic standard. ```javascript // app/index.js — bootstrap Angular avec des imports ES6 const mod = angular.module("app", [ "ionic", "ui.router", modCommon, modLogin, modHome, ]); mod.config(stateConfig); mod.run(appRun); ``` ### Organisation modulaire Chaque feature est un module autonome avec son controller, son state UI Router, son template et ses styles : ``` app/ ├── index.js # bootstrap Angular ├── Common/ │ ├── services/ │ │ └── ExternalWebPages.js │ ├── app.scss # overrides Ionic │ └── config.json ├── Login/ │ ├── Login.controller.js │ ├── Login.state.js │ ├── Login.html │ └── LoginPage.css └── Home/ ├── Home.controller.js ├── Home.state.js ├── Home.html └── Transition/ ├── Transition.controller.js ├── Transition.state.js └── universeDescription.json ``` Les controllers utilisent la syntaxe ES2015 class avec `$inject` pour la minification : ```javascript class LoginCtrl { constructor($scope, externalWebPages) { this.state = "signIn"; this.externalWebPages = externalWebPages; $scope.$on("$ionicView.beforeEnter", () => ); } toggleState() proceed(email, password) { if (this.isSignIn()) else } } LoginCtrl.$inject = ["$scope", "externalWebPages"]; ``` ### La qualité de code en garde-fou Le pipeline Webpack intègre ESLint, Stylelint (avec le preset SUIT CSS pour le BEM), PostCSS avec autoprefixer, et un truc qu'on venait de découvrir : `prettier-eslint-webpack-plugin`, qui reformate le code automatiquement à chaque build en dev. Tu enregistres un fichier mal indenté, Webpack le reformate et recharge. Ça a mis fin aux discussions sur le style de code dans l'équipe. Côté CSS, Stylelint avec `stylelint-selector-bem-pattern` impose la convention BEM. Les noms de classe qui ne suivent pas le pattern sont signalés comme des erreurs. C'est un peu rigide, mais sur un projet avec plusieurs développeurs, ça évite le chaos dans les styles. ## Le bilan : trois semaines de gagnées Le stub a pris une demi-journée à monter. Le fichier JSON de données, une journée de plus (rédiger 18 questions réalistes avec 6 réponses chacune, c'est du travail). Total : un jour et demi d'investissement. Quand le client nous a enfin donné l'accès à l'API de production, six semaines plus tard, l'app était pratiquement terminée. On a changé l'URL, lancé les tests, corrigé deux ou trois différences de format mineures (un champ en `camelCase` d'un côté et en `snake_case` de l'autre, ce genre de choses). En une journée, tout marchait. Sans le stub, on aurait attendu six semaines avant de commencer à intégrer. Le projet aurait pris neuf semaines au lieu de six. Le stub a absorbé 80% du risque d'intégration en amont, parce que les validations Swagger avaient déjà vérifié que nos requêtes respectaient le contrat. Si c'était à refaire, on referait pareil. Un jour et demi pour le stub, c'est rien comparé à six semaines à se tourner les pouces. Et le Swagger comme contrat partagé, c'est ce qui a fait que le branchement sur la vraie API s'est passé sans drame.