Article original : How to choose which validator to use: a comparison between Joi & express-validator

Par Shailesh Shekhawat

Imaginez que vous avez un site web de commerce électronique et que vous permettez aux utilisateurs de créer des comptes en utilisant leur nom et leur email. Vous voulez vous assurer qu'ils s'inscrivent avec de vrais noms, pas quelque chose comme cool_dud3.

C'est là que nous utilisons la validation pour valider les entrées et nous assurer que les données d'entrée suivent certaines règles.

Sur le marché, nous avons déjà un tas de bibliothèques de validation, mais je vais comparer deux bibliothèques de validation importantes : Joi et express-validator pour les applications basées sur express.js.

Cette comparaison est utile lorsque vous avez décidé d'utiliser une bibliothèque de validation d'entrée externe pour votre application construite sur expressjs et que vous n'êtes pas tout à fait sûr de celle à utiliser.

Qui est quoi ?

Joi

Joi vous permet de créer des plans ou des schémas pour les objets JavaScript (un objet qui stocke des informations) afin d'assurer la validation des informations clés.

Express-validator

express-validator est un ensemble de express.js middlewares qui enveloppe les fonctions de validation et de nettoyage de validator.js.

Donc par définition, nous pouvons dire que :

  • Joi peut être utilisé pour créer des schémas (comme nous utilisons mongoose pour créer des schémas NoSQL) et vous pouvez l'utiliser avec des objets JavaScript simples. C'est comme une bibliothèque plug n play et est facile à utiliser.
  • D'autre part, express-validator utilise validator.js pour valider les routes expressjs, et il est principalement construit pour les applications express.js. Cela rend cette bibliothèque plus niche et fournit une validation et un nettoyage personnalisés prêts à l'emploi. De plus, je le trouve personnellement facile à comprendre :)

Trop de méthodes et d'API pour faire certaines validations dans Joi peuvent vous submerger, donc vous pourriez finir par fermer l'onglet.

Mais je peux me tromper — alors gardons les opinions de côté et comparons les deux bibliothèques.

Instantiation

Joi

Dans Joi, vous devez utiliser **Joi.object()** pour instancier un objet de schéma Joi avec lequel travailler.

Tous les schémas nécessitent Joi.object() pour traiter la validation et d'autres fonctionnalités de Joi.

Vous devez lire séparément req.body, req.params, req.query pour le corps de la requête, les paramètres et la requête.

const Joi = require('joi');

const schema = Joi.object().keys({
   // valider les champs ici
})

Express-validator

Vous pouvez simplement requérir express-validator et commencer à utiliser ses méthodes. Vous n'avez pas besoin de lire les valeurs de req.body, req.params, et req.query séparément.

Vous devez simplement utiliser les méthodes param, query, body ci-dessous pour valider les entrées respectivement comme vous pouvez le voir ici :

const {
  param, query, cookies, header 
  body, validationResult } = require('express-validator/check')

app.post('/user', [   

// valider les champs ici

], (req, res) => {
const errors = validationResult(req);

  if (!errors.isEmpty()) {     
    return res.status(422).json({ errors: errors.array() });   
  }
}

Le champ est requis

Prenons un exemple très basique où nous voulons nous assurer qu'un username doit être une chaîne de caractères requise et est alphaNumérique avec un nombre min et max de caractères.

  • Joi :
const Joi = require('joi');
const schema = Joi.object().keys({
    username: Joi.string().alphanum().min(3).max(30).required()
})

app.post('/user', (req, res, next) => {   
  const result = Joi.validate(req.body, schema)
  if (result.error) {
    return res.status(400).json({ error: result.error });
  }
});
  • Express-validator
const { body, validationResult } = require('express-validator/check')

app.post('/user', [   
 body('username')
  .isString()
  .isAlphanumeric()
  .isLength({min: 3, max: 30})
  .exists(), 
], (req, res) => {
  const errors = validationResult(req);

  if (!errors.isEmpty()) {     
    return res.status(422).json({ errors: errors.array() });   
  }
}

Nettoyage

Le nettoyage consiste essentiellement à vérifier l'entrée pour s'assurer qu'elle est exempt de bruit, par exemple, nous avons tous utilisé .trim() sur une chaîne pour supprimer les espaces.

Ou si vous avez été confronté à une situation où un nombre arrive sous forme de "1", dans ces cas, nous voulons nettoyer et convertir le type pendant l'exécution.

Malheureusement, Joi ne fournit pas de nettoyage prêt à l'emploi, mais express-validator le fait.

Exemple : conversion en ObjectID de MongoDB

const { sanitizeParam } = require('express-validator/filter');  

app.post('/object/:id',  
   sanitizeParam('id')
  .customSanitizer(value => {
     return ObjectId(value); 
}), (req, res) => {   // Gérer la requête });

Validation personnalisée

Joi : .extend(extension)

Cela crée une nouvelle instance Joi personnalisée avec l'extension que vous fournissez incluse.

L'extension utilise certaines structures courantes qui doivent être décrites d'abord :

  • value - la valeur traitée par Joi.
  • state - un objet contenant le contexte actuel de la validation.
  • key - la clé de la valeur actuelle.
  • path - le chemin complet de la valeur actuelle.
  • parent - le parent potentiel de la valeur actuelle.
  • options - objet d'options fourni via [any().options()](https://github.com/hapijs/joi/blob/master/API.md#anyoptionsoptions) ou [Joi.validate()](https://github.com/hapijs/joi/blob/master/API.md#validatevalue-schema-options-callback).

Extension

extension peut être :

  • un seul objet d'extension
  • une fonction de fabrication générant un objet d'extension
  • ou un tableau de ceux-ci

Les objets d'extension utilisent les paramètres suivants :

  • name - nom du nouveau type que vous définissez, cela peut être un type existant. Requis.
  • base - un schéma Joi existant sur lequel baser votre type. Par défaut Joi.any().
  • coerce - une fonction optionnelle qui s'exécute avant la base, sert généralement lorsque vous souhaitez forcer les valeurs d'un type différent de votre base. Elle prend 3 arguments value, state et options.
  • pre - une fonction optionnelle qui s'exécute en premier dans la chaîne de validation, sert généralement lorsque vous devez convertir des valeurs. Elle prend 3 arguments value, state et options.
  • language - un objet optionnel pour ajouter des définitions d'erreurs. Chaque clé sera préfixée par le nom du type.
  • describe - une fonction optionnelle prenant la description entièrement formée pour la post-traiter.
  • rules - un tableau optionnel de règles à ajouter.
  • name - nom de la nouvelle règle. Requis.
  • params - un objet optionnel contenant des schémas Joi de chaque paramètre ordonné. Vous pouvez également passer un seul schéma Joi tant qu'il s'agit d'un Joi.object(). Bien sûr, certaines méthodes comme pattern ou rename ne seront pas utiles ou ne fonctionneront pas du tout dans ce contexte donné.
  • setup - une fonction optionnelle qui prend un objet avec les paramètres fournis pour permettre la manipulation interne du schéma lorsqu'une règle est définie. Vous pouvez optionnellement retourner un nouveau schéma Joi qui sera pris comme la nouvelle instance de schéma. Au moins l'un de setup ou validate doit être fourni.
  • validate - une fonction optionnelle pour valider les valeurs qui prend 4 paramètres params, value, state et options. Au moins l'un de setup ou validate doit être fourni.
  • description - une chaîne optionnelle ou une fonction prenant les paramètres comme argument pour décrire ce que fait la règle.

Exemple :

joi.extend((joi) => ({
    base: joi.object().keys({
        name: joi.string(),
        age: joi.number(),
        adult: joi.bool().optional(),
    }),
    name: 'person',
    language: {
        adult: 'doit être un adulte',
    },
rules: [
        {
            name: 'adult',
            validate(params, value, state, options) {

                if (!value.adult) {
                    // Générer une erreur, state et options doivent être passés
                    return this.createError('person.adult', {}, state, options);
                }

                return value; // Tout est OK
            }
        }
    ]
})

Express-validator

Un validateur personnalisé peut être implémenté en utilisant la méthode de chaîne [.custom()](https://express-validator.github.io/docs/validation-chain-api.html#customvalidator). Il prend une fonction de validation.

Les validateurs personnalisés peuvent retourner des Promesses pour indiquer une validation asynchrone (qui sera attendue), ou throw n'importe quelle valeur/rejeter une promesse pour utiliser un message d'erreur personnalisé.

const {
  param, query, cookies, header 
  body, validationResult } = require('express-validator/check')

app.get('/user/:userId', [   
 param('userId')
  .exists()
  .isMongoId()
  .custom(val => UserSchema.isValidUser(val)), 
], (req, res) => {

const errors = validationResult(req);

  if (!errors.isEmpty()) {     
    return res.status(422).json({ errors: errors.array() });   
  }
}

Validation conditionnelle

express-validator ne supporte pas encore la validation conditionnelle, mais il y a une PR pour cela que vous pouvez vérifier https://github.com/express-validator/express-validator/pull/658

Voyons comment cela fonctionne dans Joi :

any.when(condition, options)

**any:** Génère un objet de schéma qui correspond à n'importe quel type de données.

const schema = Joi.object({
    a: Joi.any().valid('x'),
    b: Joi.any()
}).when(
    Joi.object({ b: Joi.exist() })
    .unknown(), {
    then: Joi.object({
        a: Joi.valid('y')
    }),
    otherwise: Joi.object({
        a: Joi.valid('z')
    })
});

alternatives.when(condition, options)

Ajoute un type de schéma alternatif conditionnel, basé soit sur une autre clé (pas la même que any.when()) valeur, soit sur un schéma regardant la valeur actuelle, où :

  • condition - le nom de la clé ou référence, ou un schéma.
  • options - un objet avec :
  • is - le type joi condition requis. Interdit lorsque condition est un schéma.
  • then - le type de schéma alternatif à essayer si la condition est vraie. Requis si otherwise est manquant.
  • otherwise - le type de schéma alternatif à essayer si la condition est fausse. Requis si then est manquant.
const schema = Joi
     .alternatives()
     .when(Joi.object({ b: 5 }).unknown(), {
        then: Joi.object({
           a: Joi.string(),
           b: Joi.any()
      }),
      otherwise: Joi.object({
        a: Joi.number(),
        b: Joi.any()
      })
});

Validation imbriquée

Lorsque vous souhaitez valider un tableau d'objets/éléments ou simplement des clés d'objet

Les deux bibliothèques supportent la validation imbriquée

Et pour express-validator ?

Wildcards

Les wildcards vous permettent d'itérer sur un tableau d'éléments ou de clés d'objet et de valider chaque élément ou ses propriétés.

Le caractère * est également connu sous le nom de wildcard.

const express = require('express'); 
const { check } = require('express-validator/check'); 
const { sanitize } = require('express-validator/filter');  
const app = express(); 

app.use(express.json());  
app.post('/addresses', [   
    check('addresses.*.postalCode').isPostalCode(),
    sanitize('addresses.*.number').toInt() 
], 
(req, res) => {   // Gérer la requête });

Joi

const schema = Joi.object().keys({
    addresses: Joi.array().items(
        Joi.object().keys({
            postalCode: Joi.string().required(),
        }),
    )
});

Messages d'erreur personnalisés

Joi

any.error(err, [options])

Remplace l'erreur joi par défaut par une erreur personnalisée

let schema = Joi.string().error(new Error('Attendait VRAIMENT une chaîne de caractères'));

Express-validator

const { check } = require('express-validator/check'); 

app.post('/user', [   
   // ...d'autres validations...   
   check('password')     
   .isLength({ min: 5 }).withMessage('doit faire au moins 5 caractères de long')
   .matches(/\d/).withMessage('doit contenir un nombre') 
], 
(req, res) => {   // Gérer la requête d'une manière ou d'une autre });

Conclusion

J'ai couvert les parties les plus importantes des deux bibliothèques et vous pouvez décider vous-même celle que vous souhaitez utiliser. Veuillez me faire savoir dans les commentaires ci-dessous si j'ai oublié quelque chose d'important dans la comparaison.

J'espère que vous trouverez cela utile lorsque vous déciderez du prochain module de validation d'entrée pour votre application express.js.

J'ai écrit un article approfondi à ce sujet ici : comment valider les entrées. N'hésitez pas à le consulter.

N'hésitez pas à applaudir si vous avez trouvé cela une lecture utile !

Publié à l'origine sur 101node.io le 31 mars 2019.