Décorateurs coté serveur

J'ai démarré il y a peu un nouveau projet avec un backend en nodejs. J'en ai profité pour me remettre à jour et appliquer ce que j'ai appris coté front ces derniers mois : les décorateurs. J'ai préparé un starter project dont la particularité est d'être écrit en ES6+ transpilé avec Babel et de faire usage de décorateurs pour décrire les routes.

Pour créer un nouvel ensemble de routes, il suffit de rajouter un fichier dans le dossier server/routes/mon-controller.js et d'exporter une classe décorée avec @routes. On peut alors décorer chacune de ses méthodes avec un nom de méthode http. Ainsi :

import {routes, get} from '../decorators/routes';  
import {bikesFetcher} from '../fetchers/bikes';

@routes
export class Bikes {  
  @get({ route: '/bikes' })
  getBikes (req, res) {
    bikesFetcher.getAll()
    .then(bikes => res.json(bikes);
    .catch(err => res.status(500).send(err));
  }

  @get({ route: '/bikes/:id' })
  getBikeById(req, res) {
    bikesFetcher.getById(req.params.id)
    .then(bike => res.json(bike);
    .catch(err => res.status(500).send(err));    
  }
}

Si on fouille dans la définition de @routes on découvre que l'app express doit être passée dans le constructor et est utilisée pour définir les routes en lisant une statique _routes, elle même renseignée par chacun des décorateurs de méthode, @get par exemple. C'est lors de l'instanciation automatique de chacun des modules de routes que ceux-ci sont construits avec l'app en paramètre.

On peut passer un argument middlewares à @routes et @[method]. Il s'agit d'un ou d'un tableau de middlewares qui seront donc exécutés avant la route concernée. On peut le passer sur @routes afin qu'il s'applique à toutes les routes de la classe, ou sur une route en particulier. Par exemple avec un middleware d'authentification :

export function auth (req, res, next) {  
  if (req.user) {
    return next();
  }
  res.status(401).send('Auth failed');
}
import {routes, get} from '../decorators/routes';  
import {bikesFetcher} from '../fetchers/bikes';  
import {auth} from '../auth';

@routes
export class Bikes {  
  // […]

  @post({ route: '/bikes/:id', middlewares: auth })
  createBike(req, res) {
    bikesFetcher.create(req.body)
    .then(bike => res.json(bike);
    .catch(err => res.status(500).send(err));    
  }
}

Ici, les deux méthodes de lectures sont accessibles à tous, mais la méthode de création de vélo n'est accessible qu'avec une session valide. On pourrait rendre l'accès aux vélos entièrement inaccessible sans session en passant le middleware à la classe :

import {routes, get} from '../decorators/routes';  
import {bikesFetcher} from '../fetchers/bikes';  
import {auth} from '../auth';

@routes({ middlewares: auth })
export class Bikes {  
  // […]

  @post({ route: '/bikes/:id' })
  createBike(req, res) {
    bikesFetcher.create(req.body)
    .then(bike => res.json(bike);
    .catch(err => res.status(500).send(err));    
  }
}

Tout cela est dispo librement sur github, je vous invite à y jeter un œil et à me proposer éventuellement des pull requests ;)

Hadrien

Hi, I'm a french Javascript Lead Developer, Web Architect from Toulouse, France. I've worked for 12 years for many projects with YUI, AngularJS, Aurelia.io and now React and React native.

Toulouse, France https://hadrien.eu