On vient de livrer le back-office de Poujauran — un projet Rails 5.2, cinq modèles, zéro gem exotique. Le client est content, l’outil tourne, et le développement a pris trois semaines. On nous pose régulièrement la question : “pourquoi pas Node ?” ou “pourquoi pas Express ?”. On a des clients qui arrivent avec cette demande parce que leur CTO a lu un article sur Medium. Alors autant écrire le nôtre.

Cet article n’est pas une attaque contre Node.js ou JavaScript. On utilise React Native pour le mobile, on a livré des projets en Ionic avec des backends Express. Mais quand on a le choix du backend pour une app web — un SaaS, un back-office, une marketplace, une API — on part sur Rails. Et voici pourquoi.

Le Gemfile, c’est un catalogue de problèmes résolus

L’avantage décisif de Rails, ce n’est pas le framework lui-même. C’est l’écosystème de gems qui s’est construit autour depuis quinze ans. Des bibliothèques matures, maintenues, testées en production par des milliers d’applications.

Prenons un exemple concret. Un client nous demande un back-office de gestion d’équipes avec audit trail, soft delete, recherche avancée, pagination, validation d’emails, et machine à états sur les dossiers. En Rails, le Gemfile ressemble à ça :

# Gemfile — un projet réel, pas un exercice de style
gem "rails", "~> 5.2.2"

gem "devise"           # authentification
gem "pundit"           # autorisations
gem "audited"          # audit trail automatique
gem "paper_trail"      # versioning des modèles
gem "paranoia"         # soft delete
gem "aasm"             # machine à états
gem "ransack"          # recherche et filtres
gem "kaminari"         # pagination
gem "valid_email2"     # validation d'email (MX, disposable…)
gem "friendly_id"      # slugs lisibles dans les URL
gem "groupify"         # appartenance à des groupes
gem "sidekiq"          # jobs asynchrones

Douze gems. Chacune résout un problème que sinon il faudrait coder à la main. Et chacune s’intègre dans le modèle ActiveRecord de manière déclarative :

class Dossier < ApplicationRecord
  # audit & versioning
  audited
  has_paper_trail

  # soft delete — le dossier n'est jamais vraiment supprimé
  acts_as_paranoid

  # machine à états
  include AASM
  aasm column: :statut do
    state :brouillon, initial: true
    state :soumis, :en_cours, :cloture

    event :soumettre do
      transitions from: :brouillon, to: :soumis
    end
    event :traiter do
      transitions from: :soumis, to: :en_cours
    end
    event :cloturer do
      transitions from: :en_cours, to: :cloture
    end
  end

  # URL lisible
  extend FriendlyId
  friendly_id :reference, use: :slugged

  # recherche
  scope :search, ->(params) {
    ransack(params).result
  }

  validates :email_contact, 'valid_email_2/email': true
end

Ce modèle fait de l’audit automatique, du versioning, du soft delete, une machine à états avec transitions validées, des slugs, de la recherche, et de la validation d’email avec vérification MX. En une trentaine de lignes déclaratives. On lit le fichier, on comprend le métier.

Le même projet en Node : inventaire des décisions à prendre

Maintenant, imaginons qu’on parte sur Express.js pour le même projet. On est début 2019, Express 4.16 est la version stable, Express 5 est en alpha depuis des années.

Première question : la structure du projet. Express n’en impose aucune. Chaque développeur organise ses dossiers différemment. On a vu des projets Express où les routes sont dans app.js, d’autres dans routes/, d’autres dans api/v1/. Quand un nouveau développeur rejoint le projet, il passe une journée à comprendre l’arborescence.

Deuxième question : l’ORM. Sequelize ? TypeORM ? Knex avec des requêtes manuelles ? Sequelize 4 est stable mais verbeux. TypeORM est en 0.2 — toujours pas de version 1.0, le support TypeScript est prometteur mais la documentation a des trous. Knex n’est pas un ORM, c’est un query builder — il faut coder la couche modèle soi-même.

Troisième question : chaque fonctionnalité transverse. L’audit trail ? Il faut le coder. Le soft delete ? Il faut le coder. La machine à états ? Il existe javascript-state-machine mais ça ne s’intègre pas avec l’ORM. La pagination ? sequelize a un .findAndCountAll(), mais le rendu de la pagination dans les vues, c’est à toi. La recherche avec des filtres dynamiques ? À toi. La validation d’email avec vérification MX ? npm install email-validator — un paquet qui fait 11 lignes et qui ne vérifie que le format.

On ne dit pas que c’est impossible. On dit que chaque brique demande un choix, une intégration, et du code glue. Et au bout du compte, on a passé deux semaines à assembler la plomberie là où Rails nous laisse coder le métier dès le premier jour.

La maturité n’est pas un gros mot

Les gems qu’on utilise ne sont pas des projets de weekend. paper_trail existe depuis 2010. devise depuis 2009. friendly_id depuis 2008. kaminari depuis 2011. Ces bibliothèques ont traversé cinq versions majeures de Rails. Elles ont des centaines de contributeurs, des milliers d’applications en production, et des guides de migration documentés pour chaque montée de version.

Dans l’écosystème npm, la maturité est l’exception. La majorité des paquets sont maintenus par une seule personne, souvent sur son temps libre. Le cas event-stream en novembre dernier l’a rappelé brutalement : un mainteneur fatigué a cédé les droits de publication de son paquet à un inconnu, qui y a injecté du code malveillant ciblant un wallet Bitcoin. Le paquet avait deux millions de téléchargements par semaine.

Ce n’est pas un problème de JavaScript. C’est un problème de culture de l’écosystème. npm encourage les micro-paquets — des fonctions de trois lignes publiées comme des dépendances. On se retrouve avec des node_modules de 500 Mo pour un projet CRUD, et une surface d’attaque qu’aucun audit ne peut couvrir. Ryan Dahl lui-même, le créateur de Node, a listé node_modules parmi ses regrets lors de sa conférence à JSConf EU l’été dernier.

Ce qu’on gagne vraiment : du temps pour le métier

On ne choisit pas Rails parce que c’est hype — ça ne l’est plus depuis 2012, et c’est très bien comme ça. On le choisit parce que sur les projets qu’on fait, on livre plus vite et on maintient plus facilement.

Quelques exemples concrets de temps gagné sur des projets récents :

L’audit trail sur Poujauran. Le client voulait savoir qui avait modifié une commande et quand. Avec audited, c’est une ligne dans le modèle. La gem enregistre automatiquement chaque modification avec l’utilisateur, le timestamp, et les valeurs avant/après. En Node, il aurait fallu écrire un middleware, intercepter les writes de l’ORM, et stocker les diffs dans une table dédiée. Une journée de travail minimum, contre cinq minutes.

La recherche sur le projet Hively. L’admin voulait filtrer les prestataires par ville, type de service, note, disponibilité. Avec ransack, on déclare les filtres dans la vue, et la gem génère les requêtes SQL. Pas de code côté controller pour parser les paramètres de recherche. En Express avec Sequelize, il faut écrire le parsing des query params, construire les conditions where dynamiquement, gérer les cas vides, les combinaisons, les tris. Deux jours contre deux heures.

La machine à états sur un projet de gestion de dossiers. Un dossier passe de “brouillon” à “soumis” à “en cours” à “clôturé”. Avec aasm, on déclare les transitions, on ajoute des callbacks (envoi de mail à la soumission, notification à l’admin), et la gem garantit qu’on ne peut pas passer de “brouillon” à “clôturé” sans les étapes intermédiaires. En Node, il faut soit coder les validations de transition à la main dans chaque endpoint, soit intégrer une lib de state machine qui ne connaît rien à la base de données.

”Mais Node est plus rapide”

On l’entend souvent. Et c’est vrai — pour certaines choses. Node est excellent pour les I/O concurrentes, les WebSockets, le streaming. Si on construit un serveur de chat en temps réel ou un proxy API qui gère dix mille connexions simultanées, Node est un meilleur choix que Rails.

Mais ce n’est pas ce qu’on construit la plupart du temps. On construit des applications web avec des formulaires, des CRUD, de l’authentification, des emails transactionnels, des exports CSV, des back-offices. Et pour ça, la “vitesse” qui compte n’est pas celle de la boucle événementielle — c’est la vitesse à laquelle on livre une fonctionnalité qui marche, testée, et maintenable.

Un développeur Rails senior livre un CRUD complet avec authentification, autorisations, audit trail, recherche et pagination en une journée. Pas parce qu’il bâcle. Parce que chaque brique existe, s’emboîte, et a été testée par des milliers de projets avant le sien.

NestJS, le signe que Node a compris le problème

On suit de près NestJS, qui vient de sortir en version 6. C’est le premier framework Node qui assume d’avoir des conventions : une structure de projet imposée, de l’injection de dépendances, des modules, des décorateurs TypeScript. C’est un aveu : Express sans conventions, ça ne scale pas en équipe.

NestJS va dans la bonne direction. Mais il n’a pas (encore) l’écosystème. Les “modules” NestJS sont souvent des wrappers fins autour de bibliothèques npm qui, elles, n’ont pas la maturité des gems Rails. C’est prometteur, et on le recommandera peut-être dans quelques années. Pour l’instant, on regarde avec intérêt.

Rails en 2019 : pas mort, pas hype, efficace

Rails 5.2 est solide. Rails 6 est en beta avec des nouveautés intéressantes — Action Mailbox, Action Text, le support multi-base de données. Le framework continue d’évoluer sans casser ce qui marche.

Notre conviction est simple : pour 80% des projets web qu’on reçoit, Rails est le choix le plus productif. Pas parce que c’est notre langage préféré (Aurélien code aussi en Elixir, on fait du Swift, du Kotlin, du TypeScript). Parce que l’écosystème de gems transforme des semaines de développement en quelques lignes de configuration. Et que le temps qu’on ne passe pas à réinventer la plomberie, on le passe sur ce qui compte : le métier du client.


Si vous voulez voir Rails en action sur nos projets : le back-office Poujauran, le backend de Simone.paris, l’architecture du baby-foot connecté, et la gestion de disponibilités pour Hively.