Original article: https://www.freecodecamp.org/news/how-to-make-input-validation-simple-and-clean-in-your-express-js-app-ea9b5ff5a8a7/

Este tutorial requiere conocimientos previos sobre el uso del framework express.js

¿Por qué necesitamos la validación en el lado del servidor?

  • La validación en el lado del cliente no es suficiente y puede ser subvertida
  • Mayor propensión a ataques de intermediario. El servidor nunca debe confiar en el lado del cliente
  • Un usuario puede desactivar la validación de JavaScript del lado del cliente y manipular los datos

Si has creado aplicaciones web utilizando un framework Express o cualquier otro framework Node.js, la validación juega un papel crucial en cualquier aplicación web que requiera que valide la solicitud body param query.

Escribir tu propia función de middleware puede ser engorroso si

  • Quieres avanzar rápido a la vez que mantienes la calidad del código o
  • Quieres evitar usar if (req.body.head) o if (req.params.isCool) en tu función del controlador principal donde defines la lógica de negocios

En este tutorial, aprenderás cómo validar la entrada de datos en una aplicación Express.js utilizando un módulo popular y de código abierto llamado express-validator.

Introducción a express-validator

La definición en Github dice:

express-validator es un conjunto de middlewares express.js que envuelve el validador validator.js  y otras funciones de sanitización.

El módulo implementa cinco importantes API’s:

  • Check API
  • Filter API
  • Sanitization chain API
  • Validation chain API
  • Validation Result API

Demos un vistazo a la creación de un usuario mediante una route básica sin ningún módulo de validación para crearlo: /route/user.js

/**
* @api {post} /api/user Create user
* @apiName Create new user
* @apiPermission admin
* @apiGroup User
*
* @apiParam  {String} [userName] username
* @apiParam  {String} [email] Email
* @apiParam  {String} [phone] Phone number
* @apiParam  {String} [status] Status
*
* @apiSuccess (200) {Object} mixed `User` object
*/

router.post('/', userController.createUser)

Ahora en el controlador de usuario /controllers/user.js

const User = require('./models/user')

exports.createUser = (req, res, next) => {
  /** Aquí debe validar la entrada del usuario. 
    Sólo el Nombre del usuario y el email serán campos requeridos
 */
  
  const { userName, email, phone, status } = req.body
  if (userName && email &&  isValidEmail(email)) { 
    
    // isValidEmail es una función personalizada que es necesario escribir desde cero o usar un módulo npm para validar el email 
    User.create({
      userName,
      email,
      phone,
      status,   
    })
    .then(user => res.json(user))
    .catch(next)
  }
}

El código de arriba es solo un ejemplo básico de validación de campos personalizados.

Puedes implementar algunas validaciones en tu modelo de usuario usando Mongoose. Una buena práctica es asegurarse de que la validación ocurra antes que la lógica de negocios.

express-validator se encargará de todas estas validaciones y también de la sanitización de las entradas

Instalación

npm install --save express-validator

Incluye módulo de tu archivo principal server.js:

const express = require('express')
const bodyParser = require('body-parser')
const expressValidator = require('express-validator')
const app = express()
const router = express.Router()

app.use(bodyParser.json())

app.use(expressValidator())

app.use('/api', router)

El uso de express-validator. El archivo /routes/user.js quedaría así:

router.post(
  '/', 
  userController.validate('createUser'), 
  userController.createUser,
)

userController.validate es una función middleware que se explica a continuación. Acepta el nombre del method para el cual se usará la validación.

La función middleware  validate() sería la siguiente en nuestro /controllers/user.js:

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

exports.validate = (method) => {
  switch (method) {
    case 'createUser': {
     return [ 
        body('userName', 'userName doesn't exists').exists(),
        body('email', 'Invalid email').exists().isEmail(),
        body('phone').optional().isInt(),
        body('status').optional().isIn(['enabled', 'disabled'])
       ]   
    }
  }
}

La función body sólo válida req.body y se le pasan dos argumentos. El primero es property name. Y el segundo es un mensaje personalizado message que se mostrará si la validación falla. Si no se proporciona un mensaje personalizado, mostrará un mensaje por defecto.

Como hemos podido ver, para un campo required usamos el método .exists() . Usamos .optional() para un campo optional. Igualmente, isEmail()  e isInt() sirven para email e integer.

Si quieres definir una entrada que admita sólo ciertos valores, podemos usar .isIn([]). Esta función toma un array de valores, y si recibe otros valores lanza un error.

Por ejemplo, el campo status del código de arriba sólo puede tener un valor binarioenabled o disabled. Si el usuario proporciona otro valor, la función lanza un error.

Dentro de /controllers/user.js añadimos una función createUser dónde describimos la lógica de negocios. Esta puede llamarse después de  validate() con el resultado de las validaciones.

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

exports.createUser = async (req, res, next) => {
   try {
      const errors = validationResult(req); // Encuentra los errores de validación en esta solicitud y los envuelve en un objeto resultante de una práctica función

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

      const { userName, email, phone, status } = req.body
      
      const user = await User.create({

        userName,

        email,

        phone,

        status,   
      })

      res.json(user)
   } catch(err) {
     return next(err)
   }
}

Si te preguntas qué es validationResult(req)...

Esta función encuentra los errores de validación en la solicitud y los envuelve en un objeto

Ahora, siempre que en la solicitud req.body se incluyan parámetros no válidos o falte el campo userName, el servidor responderá lo siguiente:

{
  "errors": [{
    "location": "body",
    "msg": "userName is required",
    "param": "userName"
  }]
}

A su vez, si falla la validación de userName o email cada error devuelto por el método, .array() tendrá el siguiente formato por defecto:

{   
  "msg": "El mensaje de error",
   
  "param": "nombre del parámetro", 
  
  "value": "valor del parámetro",   
  // Location del parámetro que ha generado el error.   
  // Sus correspondientes body, query, params, cookies o headers.   
  "location": "body",    
  
 // nestedErrors sólo existe cuando se usa la función oneOf 
  "nestedErrors": [{ ... }] 
}

Como hemos podido ver, este módulo nos ayuda con la mayor parte de las validaciones personalizadas, permitiéndonos mantener un código de calidad centrado principalmente en la lógica de negocios.

Esta ha sido una introducción a la validación de entradas usando el módulo express-validator y en la parte 2 de esta serie de artículos podrás ver cómo hacer una validación personalizada de una matriz de elementos y más

Artículo original en inglés publicado en 101node.io el 2 de septiembre del 2018.