Écrire un service REST avec NodeJS et Express – partie 2/3: jouons avec les middlewares

Dans la première partie, nous avions écrit (en mode TDD un peu raccourci) un service REST. On est quand-même allés au bout, mais ça restait améliorable.

Dans cette seconde partie nous allons jouer avec les middlewares Express pour factoriser, ajouter diverses vérifications sur les données, etc… Afin de ne pas imposer de pavés trop lourd, je garderai finalement pour un 3e article l’intégration du support de nouveaux formats, et l’écriture d’une doc agréable à utiliser.

Sommaire

Rappel sur les middlewares dans Express

Il existe deux types de middlewares:

// Middleware de gestion d'erreur
function middleware_error (err, req, res, next) {
  ...
}
 
// Middleware standard
function middleware (req, res, next) {
  ...
}

Voici une explication détaillée du rôle de chaque paramètre:

Paramètre Rôle Usage
err L’erreur précédente Il s’agit de l’erreur qui a été levée par le précédent middleware (exception lancée, ou appel à next(error)). Dans le cas d’un middleware de gestion d’erreur elle est donc évidemment toujours définie.
req La requête L’objet ServerRequest enrichi par Express, permettant d’accéder aux données de la requête
res La réponse L’objet ServerResponse permettant de construire la réponse à renvoyer au client
next La fonction de passage de relais Cette fonction est extrêmement importante: appeler next() passe la main au prochain middleware standard, alors qu’appeler next(error) passera la main au prochain middleware de gestion d’erreur.

Une middleware a deux buts dans la vie: répondre au client, ou déléguer au prochain middleware. Donc si vous ne voulez pas que votre client se retrouve perdu, il faut toujours absolument appeler next([error]) ou res.end(). On va profiter de cet article pour bien jouer avec le placement des middlewares, de manière à obtenir les comportements souhaités!

Gestion des erreurs

Par défaut, Express enregistre ses propres error handlers (voir node_modules/express/node_modules/connect/lib/middlewares/errorHandler.js), dont nous allons nous passer car ils ont le défaut de ne pas retourner l’information dans le format qu’on souhaite. On va donc écrire notre propre gestionnaire d’erreur.

Généralement, on souhaite faire 3 choses avec une erreur:

  • La retourner à l’utilisateur.
  • Uniquement en environnement de développement, fournir des détails à l’utilisateur (comme la stack).
  • La logger quelque part.

Écrivons une fonction qui va générer notre errorHandler en fonction des options qu’on lui donne (notamment personnalisation du logger, et affichage ou non de la stack dans la réponse), et qui aura la possibilité de prendre en compte un attribut spécifique qu’on affectera à nos exceptions pour définir le code de retour HTTP:

function errorHandler (options) {
  var log = options.log || console.error
    , stack = options.stack || false
  return function (err, req, res, next) {
    log(err.message);
    if (stack && err.stack) log(err.stack);
    var content = err.message;
    if (stack && err.stack) content += '\n' + err.stack;
    res.respond(content, err.code || 500);
  }
}

Puis on remplace les errorHandlers originaux par le notre:

25
26
27
28
29
30
31
app.configure('development', function(){
  app.use(errorHandler({"stack": true}));
});
 
app.configure('production', function(){
  app.use(errorHandler());
});

Ajout de vérifications en amont

On souhaite faire des vérifications sur les entêtes fournies par le client: il faut dans tous les cas qu’il accepte le format application/json en réponse, et dans le cas des méthodes nécessitant des données à envoyer (en l’occurrence PUT et POST), il doit spécifier le Content-Type application/json.

D’abord, complétons nos tests unitaires dans test/rest-test.js:

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
.root('/bookmarks')
 
// Initially: start server
.expect('Start server', function () {
  app.db.configure({namespace: 'bookmarks-test-rest'});
  app.listen(PORT);
}).next()
 
// 0. Test header errors
.get().expect(406) // Invalid headers
.setHeader('Accept', 'application/json')
.get().expect(200).next() // OK
.put().expect(406) // Invalid headers
.setHeader('Content-Type', 'application/json')
.put().expect(405).next() // Method not allowed
 
// 1. Empty database

On va donc écrire notre middleware de vérification, qui doit répondre avec le code de statut 406:

function checkRequestHeaders (req, res, next) {
  if (!req.accepts('application/json'))
    return res.respond('You must accept content-type application/json', 406);
  if ((req.method == 'PUT' || req.method == 'POST') && req.header('content-type') != 'application/json')
    return res.respond('You must declare your content-type as application/json', 406);
  return next();
}

Puis on va le placer en tout début de liste:

18
19
20
  app.set('view engine', 'ejs');
  app.use(checkRequestHeaders);
  app.use(express.bodyParser());

On exécute nos tests avec vows pour vérifier qu’on a bien le bon comportement. Le code 406 est maintenant implémenté :)

Validation du format des données

Comme on l’avait vu, on peut donner à peu près ce qu’on veut à manger à notre service, on peut même lui servir un objet vide… Aucune validation n’est faite sur le modèle des données! Il faut effectuer cette vérification, qu’on effectuera juste après le parsing lui-même.

Comme d’habitude, on commence par mettre à jour nos tests:

30
31
32
33
34
35
36
37
// Invalid data ↓
.post({"toto":"tata"}).expect(400).next()
.post({}).expect(400).next()
.post({"url":"url","title":"title","tags":"some tag"}).expect(400).next()
.post({"url":"url","title":"title","extra":"attribute"}).expect(400).next()
// Valid data ↓
.post({"url":"url1","title":"title1"}).expect(200).next()
.post({"url":"url2","title":"title2","tags":["some tag"]}).expect(200).next()

Un peu plus loin, on a un test à modifier, puisque maintenant on vérifie en amont que POST et PUT sont bien accompagnées de données:

103
104
105
// 8. Test unallowed methods
.post('/bookmark/' + expected_id, bookmark).expect(405)
.put(bookmark).expect(405)

Pour faire de la validation de données on va en profiter pour jouer avec un autre module :) npm install contracts pour profiter de cette librairie de validation des modèles. Puis on écrit notre middleware:

var contracts = require('contracts');
function checkRequestData (req, res, next) {
  if (req.method == 'POST' || req.method == 'PUT') {
    // Body expected for those methods
    if (!req.body) return res.respond('Data expected', 400);
    var required = req.method == 'POST'; // PUT = partial objects allowed
    // Validate JSON schema against our object
    var report = contracts.validate(req.body, {
      "type": "object",
      "additionalProperties": false,
      "properties": {
        "url":   { "type": "string", "required": required, "format": "url" },
        "title": { "type": "string", "required": required },
        "tags":  { "type": "array", "items": { "type": "string" }, "required": false }
      }
    });
    // Respond with 400 and detailed errors if applicable
    if (report.errors.length > 0) {
      return res.respond('Invalid data: ' + report.errors.map(function (error) {
        var message = error.message;
        if (~error.uri.indexOf('/')) {
          message += ' (' + error.uri.substring(error.uri.indexOf('/')+1) + ')';
        }
        return message;
      }).join(', ') + '.', 400);
    }
  }
  next();
}

On vérifie que nos tests passent bien tous avec vows.

C’est bon. On a pris la peine de construire de jolis messages d’erreurs détaillés grâce à contracts, donc on va tout de même vérifier que ça marche 😉

curl 'http://localhost:3000/bookmarks' -H 'Content-Type: application/json' -H 'Accept: application/json' -X POST -d '{"url":"coucou"}'
# Réponse: {"code":400,"status":"Bad Request","message": \
# "Invalid data: String is not in the required format (url), Property is required (title)."}

Compléter les codes d’erreur

On a plutôt bien avancé, néanmoins il reste un problème: quand on envoie de la donnée syntaxiquement incorrect (par exemple '{"url":"je ne ferme pas mes guillemets ni mes accolades'), ça nous renvoie actuellement une erreur 500.

On va d’abord commencer par mettre à jour les tests unitaires… Oh wait! API-Easy ne permet pas d’envoyer de la donnée sous forme de chaine de caractère, son API ne prend en compte la data que sous forme d’objet. Aïe, c’est moche! On se contentera donc des tests manuels.

Là le jeu va être d’intercepter une éventuelle erreur levée par express.bodyParser(), pour la passer d’une erreur 500 à une erreur 400. C’est en fait très simple: il suffit d’ajouter un middleware de gestion d’erreur juste après bodyParser, comme ça on intércepte son erreur SyntaxError (ce qui permet de laisser les autres types d’erreur inattendues en 500).

function handleBodyParserError (err, req, res, next) {
  if (err instanceof SyntaxError) res.respond(err, 400);
  else next(err);
}
18
19
20
21
22
23
  app.set('view engine', 'ejs');
  app.use(checkRequestHeaders);
  app.use(express.bodyParser());
  app.use(handleBodyParserError); // ← ICI
  app.use(checkRequestData);
  app.use(express.methodOverride());

On test avec curl:

curl 'http://localhost:3000/bookmarks' -H 'Content-Type: application/json' -H 'Accept: application/json' \
-X POST -d '{"url":"coucou'
# Réponse: {"code":400,"status":"Bad Request","message":"SyntaxError: Unexpected token ILLEGAL"}

On a maintenant une réponse plus cohérente: en effet le fait d’envoyer de la donnée invalide n’est pas une erreur du serveur (500), mais bien un problème de requête (400).

Un peu de refactoring

On va maintenant tailler dans le vif, en réorganisant tout le code, toujours grâce à l’utilisation des middlewares. Évidemment à chaque étape on n’oubliera pas de rééxécuter nos tests unitaires pour vérifier qu’on n’a rien cassé. C’est l’avantage évident de faire du refactoring uniquement une fois que les tests sont tous au vert 😉

Validation des paramètres

Express apporte une facilité pour valider les paramètres d’URL en fonction de leur nom. En l’occurrence on utilise trois fois :id en le validant de la même manière. On va donc factoriser cette partie:

function checkIdParameter (req, res, next, id) {
  if (isNaN(parseInt(id))) {
    return next({"message": "ID must be a valid integer", "code": 400});
  }
  next();
}
16
17
app.configure(function(){
  app.param('id', checkIdParameter);

On peut alors supprimer cette vérification de nos 3 méthodes concernées.

Gérer les erreurs 404

Toujours au niveau des méthodes gérant des entités, on traite les 404 avec un test if (err.type == 'ENOTFOUND') res.respond(...). On pourrait laisser faire l’error handler qu’on a écrit, en remplaçant:

res.respond(content, err.code || 500);

par:

var code = err.code || (err.type == 'ENOTFOUND' ? 404 : 500);
res.respond(content, code);

On peut alors retirer notre gestion de cette erreur dans les 3 méthodes concernées, en appelant simplement next(err).

Valider la cohérence de l’ID dans la méthode de mise à jour

On a suivi pour l’instant une démarche de déplacement des opérations de vérification dans les middlewares. Cette démarche est très pertinente, car elle assure que si une erreur est détectée la route n’est pas atteinte du tout. Ça évite bien des effets de bord éventuel en cas de mauvaise organisation du code. Il reste une dernière de ces validations:

app.put('/bookmarks/bookmark/:id', function (req, res) {
  var id = req.param('id');
  if ('undefined' != typeof req.body.id && req.body.id != id) {
    res.respond(new Error('Invalid bookmark ID'), 400);
  } ...

On va donc sortir cette validation dans notre validation du paramètre « id« . On va faire de même pour le test présent dans la méthode post /bookmarks:

function checkIdParameter (req, res, next, id) {
  if (isNaN(parseInt(id))) {
    return next({"message": "ID must be a valid integer", "code": 400});
  }
  // Update
  if (req.method == 'PUT') {
    if ('undefined' == typeof req.body.id) {
      req.body.id = req.param('id'); // Undefined, use URL
    } else if (req.body.id != req.param('id')) {
      return next({"message": "Invalid bookmark ID", "code": 400}); // Defined, and inconsistent with URL
    }
  }
  // Create
  if (req.method == 'POST') {
    if ('undefined' != typeof req.body.id) {
      return next({"message": "Bookmark ID must not be defined", "code": 400});
    }
  }
  // Everything went OK
  next();
}

Nettoyage de la configuration

On n’a pas besoin de la route GET /:

app.get('/', function(req, res){
  res.render('index', {
    title: 'Express'
  });
});

ni des configurations spécifiques aux templates:

  app.set('views', __dirname + '/views');
  app.set('view engine', 'ejs');

et encore moins de servir des fichiers statiques:

  app.use(express.static(__dirname + '/public'));

Factoriser les routes

À ce stade, si vous avez bien suivi, vos déclarations de route devraient être les suivantes:

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
app.post('/bookmarks', function (req, res, next) {
  db.save(req.body, function (err, bookmark, created) {
    err ? next(err) : res.respond(bookmark);
  });
});
 
app.get('/bookmarks', function (req, res, next) {
  db.fetchAll(function (err, ids) {
    var uri = function (id) { return '/bookmarks/bookmark/' + id; };
    err ? next(err) : res.respond(ids.map(uri));
  });
});
 
app.get('/bookmarks/bookmark/:id', function (req, res, next) {
  db.fetchOne(req.param('id'), function (err, bookmark) {
    err ? next(err) : res.respond(bookmark);
  });
});
 
app.put('/bookmarks/bookmark/:id', function (req, res, next) {
  db.save(req.body, function (err, bookmark, created) {
    err ? next(err) : res.respond(bookmark);
  });
});
 
app.del('/bookmarks', function (req, res, next) {
  db.deleteAll(function (err, deleted) {
    err ? next(err) : res.respond(deleted);
  });
});
 
app.del('/bookmarks/bookmark/:id', function (req, res, next) {
  db.deleteOne(req.param('id'), function (err, deleted) {
    err ? next(err) : res.respond(deleted);
  });
});

Waouh, n’est-ce pas? Belle cure d’amaigrissement. Et on se rend compte qu’on a maintenant 6 routes qui sont quasiment jumelles! C’est normal puisque sur le principe, nos routes ne sont que des interfaces à des méthodes de la BDD. Maintenant on a supprimé tout le code « parasite » (validation, traitement d’erreur…) non lié à la logique métier. Nous allons écrire une fonction qui a pour rôle de générer un middleware lié à une méthode de la BDD.

/**
 * Middleware defining an action on DB
 * @param action The DB instance
 * @param action The action ("save", "deleteOne", "fetchAll", etc...)
 * @param filter An optional filter to be applied on DB result
 * @return Connect middleware
 */
function dbAction (db, action, filter) {
  // Default filter = identity
  filter = filter || function (v) { return v; };
  return function (req, res, next) {
    var params = [];
    // Parameters depend of DB action
    switch (action) {
      case 'save':      params.push(req.body);        break;
      case 'fetchOne':
      case 'deleteOne': params.push(req.param('id')); break;
    }
    // Last parameter is the standard response
    params.push(function (err, result) {
      err ? next(err) : res.respond(filter(result));
    });
    // Execute DB action
    db[action].apply(db, params);
  }
}

Nos routes deviennent:

36
37
38
39
40
41
42
app.post('/bookmarks', dbAction('save'));
app.get('/bookmarks', dbAction(db, 'fetchAll', function (ids) { return ids.map(function (id) { return '/bookmarks/bookmark/' + id; }); }));
app.get('/bookmarks/bookmark/:id', dbAction(db, 'fetchOne'));
app.put('/bookmarks/bookmark/:id', dbAction(db, 'save'));
app.del('/bookmarks', dbAction(db, 'deleteAll'));
app.del('/bookmarks/bookmark/:id', dbAction(db, 'deleteOne'));
app.all('/bookmarks/?*', function (req, res) { res.respond(405); });

Exporter notre application en tant que module réutilisable

Express est tellement souple qu’il accepte même une application en tant que middleware. Cela permet de brancher des serveurs sur votre application Express de manière très simple avec app.use('/route', autre_app).

On va pouvoir dissocier la partie purement « API REST » de la partie « site qui héberge cette API ». Pour ce faire, on va dans notre application se débarasser encore un peu plus du superflu: supprimer le préfixe « /bookmarks » de toutes nos routes, et prendre en compte un potentiel « app.route » qui serait définit si notre application était montée sur un autre serveur. On va renommer notre fichier bookmarks-rest.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
var express = require('express')
  , app = module.exports = express.createServer()
  , db = app.db = require('./db')()
  , m = require('./middleware')
 
app.on('close', db.close); // Close connection when server exits
 
app.configure(function () {
  app.param('id', m.checkIdParameter);
  app.use(m.checkRequestHeaders);
  app.use(express.bodyParser());
  app.use(m.handleBodyParserError);
  app.use(m.checkRequestData);
  app.use(express.methodOverride());
  app.use(app.router);
});
app.configure('development', function () {
  app.use(m.errorHandler({"stack": true}));
});
app.configure('production', function () {
  app.use(m.errorHandler());
});
 
app.post('/',             m.dbAction(db, 'save'));
app.get( '/',             m.dbAction(db, 'fetchAll', function (ids) { return ids.map(function (id) {
  // URIs now depend of the parent server
  return (app.route + (app.route.match(/\/$/) ? '' : '/') + 'bookmark/' + id; }); }));
app.get( '/bookmark/:id', m.dbAction(db, 'fetchOne'));
app.put( '/bookmark/:id', m.dbAction(db, 'save'));
app.del( '/',             m.dbAction(db, 'deleteAll'));
app.del( '/bookmark/:id', m.dbAction(db, 'deleteOne'));
app.all( '/*',            function (req, res, next) { next({"code":405, "message":"Method not allowed"}); });
 
if (module.parent == null) app.listen(3000);

On peut démarrer notre serveur directement avec node bookmarks-rest, et l’utiliser avec curl. Tout est OK. Ensuite on va re-créer notre « vrai » serveur, qui intégrera à la fois un site web qui présente le service, et le service lui-même sur « /bookmarks« . Ce sera notre nouveau app.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var express = require('express')
  , app = module.exports = express.createServer()
 
app.configure(function () {
  app.set('views', __dirname + '/views');
  app.set('view engine', 'jade');
  app.use(app.router);
  app.use(express.static(__dirname + '/public'));
});
app.configure('development', function () {
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('production', function () {
  app.use(express.errorHandler());
});
 
// Montage de l'API REST sur /bookmarks
app.use('/bookmarks', app.bookmarks_app = require('./bookmarks-rest'));
 
// Homepage
app.get('/', function (req, res) {
  res.render('index', { "title": 'Bookmarks' });
});
 
if (module.parent === null) {
  app.listen(3000);
  console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
}

Si on exécute nos tests unitaires, ils échouent tous parce qu’ils ne trouvent plus app.db! C’est normal, rest-test fait encore un require('../app') et cherche ensuite à configurer app.db. On va donc dans un premier temps doubler nos tests:

  • On doit effectuer tous les tests sur la racine / en démarrant directement ../bookmarks-rest.
  • Et les mêmes tests sur la racine /bookmarks en démarragt ../app pour vérifier que le montage fonctionne comme prévu.

Personnellement je suis passer par un « générateur de suite de tests » pour éviter les copier-coller. Vous pouvez voir le code final des tests ici.

L’éternelle histoire des ouvertures et fermetures de connexion dans nos tests unitaires

À ce stade du développement, les tests ne fonctionnent plus, quand on exécute vows ça semble se bloquer en plein milieu :( En effet, comme require() a la bonne habitude de mettre en cache les modules pour ne pas dupliquer les instances, on appelle ça… un singleton :) Et on sait que les singletons et les tests unitaires sont rarement copains. En l’occurrence le singleton qui nous dérange est sur bookmarks-rest et je vais vous détailler pourquoi ça coince, car ce n’est pas forcément évident, même si par contre la solution – elle – coule de source:

  1. Au début de app-test, le service est démarré, la connexion à Redis aussi.
  2. À la fin de app-test, on coupe le service, et la connexion à Redis avec.
  3. Vient ensuite rest-test, qui démarre un autre service, avec la même instance de bookmarks-rest, or à ce stade la connexion à Redis n’est pas relancée.
  4. Rien dans notre code ne spécifie de re-connexion forcée, puisque la connexion à Redis se fait toute seule lors du createClient(). D’ailleurs une fois la connexion fermée, le stream interne est fermé, et aucune API externe ne nous permet de le recréer, il faudrait donc recréer le client.
  5. On empile donc des commandes Redis mises en pool, mais jamais exécutées, ce qui passe nos tests en mode idle jusqu’à ce que la connexion soit rouverte, sauf que comme elle a été fermée à dessein, la reconnexion automatique en cas d’erreur ne se fera pas, et on peut attendre indéfiniment 😉

La source du problème est identifiée, il y aurait sans doute de nombreuses manières de le traiter, mais il y a une manière simple et générique: si on manipule des connexions, on devrait toujours instancier les modules, et ne pas manipuler des singletons.

Donc transformer votre module de ce type:

exports.toto = "tata"

Par quelque chose de ce genre:

module.exports = function (options) {
var my = {};
 
my.toto = "tata";
 
return my;
}

Ainsi on ne va plus récupérer un singleton avec require('module') mais une instance avec require('module')(). Pas besoin de sortir l’artillerie des classes, qui ne sont pas le point fort de Javascript. Une simple orientation fonctionnelle explicite est bien suffisante. Si vraiment vous voulez de l’orienté objet rien ne vous empêche de faire quelque chose de ce genre, mais le gain n’est pas évident:

module.exports = function (options) {
  return new MyModule(options);
}
 
function MyModule (options) {
}
 
MyModule.prototype.toto = "tata";

Bref, on applique ces modifications à notre code de manière à ce que le module bookmarks-rest soit maintenant instancié, ainsi nos deux tests ne manipulent plus la même copie de ce module, et donc plus non plus la même instance du client Redis, ce qui règle le problème de statut idle infini.

Conclusion

Vous pouvez consulter le code source final sur GitHub:

Bonnes pratiques pour les tests unitaires

La règle d’or, si vous voulez construire des modules testables unitairement, est de les rendre instanciables si nécessaire. C’est nécessaire quand:

  • Ils sont configurables. Si vous écrivez à un moment exports.options = ..., dites-vous bien que « YOU’R DOIN IT WRONG! »
  • Ils utilisent une connexion à un flux de données susceptible d’être ouvert ou fermé depuis l’extérieur. Dans ce cas si vous devez pouvoir manipuler plusieurs instances de ces connexions, et donc votre module doit être instanciable!

Comme on l’a vu ça peut se faire en transformant notre module en fonction, ou en présentant une classe à instancier au lieu de l’API directe. Peu importe la manière au final c’est une question de goût, mais il vaut mieux considérer que ce n’est pas facultatif 😉

Le résultat final

Après cette grosse phase de refactoring et d’interception des différents états de l’application grâce aux middlewares, on a une application très proche de la logique d’un service REST:

  • Une couche de manipulation directe des données
  • Une application qui n’est qu’une série d’interfaces très légères à cette couche d’accès, et très normée, ce qui ressort bien par le fait qu’on a des routes qui se résument à la simple information « quelle action j’effectue sur les données »
  • Une série de règles permettant de valider l’utilisation de cette interface, centralisées dans notre module middleware

Le résultat final est donc conceptuellement très proche de la façon dont on décrirait un service REST HTTP de manière simple. Je pense qu’on a donc touché au but :)

Dans le prochain et dernier article, on intégrera le support du format XML (parce que c’est triste, mais il y en a qui aiment encore ce format), et on parlera de l’importance de la documentation, et de comment en écrire une un peu sexy et utile.

8 réflexions au sujet de « Écrire un service REST avec NodeJS et Express – partie 2/3: jouons avec les middlewares »

  1. Ludo

    Hello,
    merci pour ces ressources concrètes et très sympa. C’est exactement ce qu’il manque à node pour le moment. Ca me permet de me rassurer pas mal et aussi de découvrir.

    As tu des liens d’autres sites/blog qui propose ce genre d’use case ? même en anglais ?

    Merci encore.

    Répondre
    1. naholyr Auteur de l’article

      Je n’en consulte en fait pas tellement, mes principales ressources d’apprentissage sont:

      Sinon il y a aussi le blog d’Atinux et d’autres dans la partie « Liens » de la colonne de droite 😉

      Bienvenue :)

      Répondre
  2. Greg

    Salut !

    J’ai parcouru avec beaucoup d’attention les deux premières parties de cet article très bien écrit (c’est rare d’en trouver d’un tel niveau en français !).

    Je découvre node.js et on approche à travers cet exemple un « idéal » que je cherche à atteindre : créer une API Rest uniquement à travers la définition des tables qui composent mon modèle (un json table/champs/type en gros).

    Voilà ce qu’il me semble manquer pour arriver à un tel résultat :
    – un routage dynamique
    – des tests unitaires dynamiques
    – sortir les règles de validation de checkRequestData

    Cette approche te semble t-elle viable et intéressante ?

    Et, si oui, sais-tu si certains frameworks/modules permettent d’arriver à ce résultat ?

    Merci pour ta réponse ! 😉

    Répondre
    1. naholyr Auteur de l’article

      Merci pour ce retour encourageant :)

      • Pour le routage, jette un œil au module « restify », je pense qu’il sera intéressant :)
      • Pour les tests unitaires dynamiques, je ne connais pas de solution, mais en même temps les tests basiques (ceux qui seraient générés) sont tellement simples à taper que je n’ai pas cherché…
      • Pour checkRequestData, la question de la refactorisation est intéressante: y a-t-il dans le code de la fonction des choses utilisables à l’extérieur ? Pas vraiment, pas vraiment de besoin de la sortir pour réutilisabilité (et si le cas se présente plus tard il sera toujours temps de le faire). Y a-t-il des tests dépendants du contexte ? Non plus, donc pas de raison pour l’instant d’en écrire plusieurs versions. Du coup pour moi pas de refactoring à faire en l’état sur cette fonction. Par contre elle pourrait éventuellement changer de format à mesure que l’application grandit en effet. Et surtout elle est clairement insuffisante pour traiter du « vrai » REST (je crois qu’il y a des commentaires intéressants à ce sujet dans l’article partie 1).
      Répondre
  3. cthiebot

    Salut,

    Un grand merci pour ces tutos qui sont vraiment super!
    Un seul regret… Je ne trouve pas la partie 3, j’espérais voir comment intégrer le supper de XML et surtout j’aurais aimé que ça parle de l’authentification!

    Encore merci pour tout ça :p

    Répondre
      1. Greg

        Slt !
        Les 2 premières parties ont leur bien sympa !
        Mais pas de 3eme partie… La question est donc est ce bien de lire les 2 premieres parties sans avoir la fin de histoire ?

        Bonne continuation.

        Répondre
        1. naholyr Auteur de l’article

          Oui oui, la troisième partie ne verra probablement jamais le jour d’ailleurs il faut se rendre à l’évidence ^^

          Donc ne t’en prive pas 😉

          Répondre

Laisser un commentaire