Formulario de validación de datos en Node.js con express-validator

En este tutorial, cubriremos cómo realizar la validación de datos de formulario en Node.js con express-validator, un contenedor para la popular biblioteca Validator.js, con ejemplos.

Introducción

Es imperativo llevar a cabo la validación del lado del servidor al crear aplicaciones, especialmente las aplicaciones orientadas al cliente. La razón es que uno nunca puede confiar solo en la entrada del usuario; ya que estas entradas a veces contienen datos falsos o maliciosos.

La validación del lado del cliente es una excelente manera de filtrar la mayor parte de la entrada, pero aún debe realizar la validación del lado del servidor también.

Hay muchas formas de validar datos en Node.js y en este artículo, veremos validador expreso. Express-validator es una biblioteca que envuelve validator.js y expone sus funciones como un conjunto de middlewares.

Configuración del proyecto

Para este tutorial, crearemos un servidor back-end de demostración para simular el registro de usuarios y el inicio de sesión con Node.js. Estos campos harán cumplir ciertas reglas y validaremos los datos que lleguen.

Tenga en cuenta que no manejaremos el registro de usuario real y la lógica de inicio de sesión, es decir, guardaremos los datos del usuario e implementaremos la autenticación, ya que esto está fuera del alcance de este artículo.

Para comenzar, crearemos una carpeta de proyecto, navegaremos hasta ella e inicializaremos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Create the project folder
$ mkdir express-validator-tut

# Navigate into the project folder
$ cd express-validator-tut

# Initialize project
$ yarn init -y
# OR
$ npm init -y

Cuando haya terminado, instalaremos las siguientes dependencias ejecutando el siguiente comando:

1
2
3
$ yarn add body-parser express express-validator
# OR
$ npm i body-parser express express-validator

Echemos un vistazo a lo que hemos instalado:

  • express: Un marco de aplicación web ligero para Node.js. Usaremos esto para manejar el enrutamiento en nuestro servidor backend.
  • body-parser: Un middleware que nos ayudará a analizar las entradas de solicitudes entrantes (entradas de usuario) en el objeto req.body.
  • express-validator: La biblioteca que usaremos para manejar la validación de entrada entrante.

Por último, crearemos un archivo index.js en nuestro directorio de proyectos para alojar el código repetitivo para la instanciación de una aplicación/servidor Express:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// index.js
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const port = 2022;

app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.post('/register', (req, res) => {});
    
app.listen(port);
console.log('See where it all happens at http://localhost:'+port);

Ahora, ejecutemos esta aplicación con node:

1
$ node index.js 

Si todo va bien, su terminal debería mostrar algo como:

expressjs boilerplate server prompt

Reglas de validación estándar con express-validator

En esta sección, aprenderemos cómo agregar reglas simples de validación y saneamiento a las solicitudes entrantes. En primer lugar, queremos verificar si el valor ingresado en el campo de correo electrónico es un correo electrónico válido o no. Luego, querremos hacer cumplir que la contraseña contenga al menos 6 caracteres.

Para comenzar, agreguemos un par de funciones de middleware a nuestra ruta /login:

 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
// index.js
...
const { body, validationResult } = require('express-validator');

app.post('/login',
    body('email').isEmail().normalizeEmail(),
    body('password').isLength({
        min: 6
    }),
    (req, res) => {
        const errors = validationResult(req);

        if (!errors.isEmpty()) {
            return res.status(400).json({
                success: false,
                errors: errors.array()
            });
        }

        res.status(200).json({
            success: true,
            message: 'Login successful',
        })
    });
...

En el fragmento anterior, utilizamos dos métodos de validación:

  • isEmail(): esta función de validación verifica si la cadena entrante es una dirección de correo electrónico válida.
  • isLength(): este validador verifica si la longitud de una cadena se encuentra dentro de un rango específico. En nuestro caso, el rango especificado es de un mínimo de 6 caracteres.

Algunos de los otros métodos que podríamos haber usado son:

  • isNumeric() - Comprueba si la entrada es numérica
  • contains() - Comprueba si la entrada contiene un cierto valor
  • isBoolean() - Comprueba si la entrada es un valor booleano
  • isCurrency() - Comprueba si la entrada tiene formato de moneda
  • isJSON() - Comprueba si la entrada es JSON
  • isMobilePhone() - Comprueba si la entrada es un número de teléfono móvil válido
  • isPostalCode() - Comprueba si la entrada es un código postal válido
  • isBefore() y isAfter() - Comprueba si una fecha es anterior o posterior a otra fecha

Hay otros, pero estos son probablemente los que cubren la mayoría de sus necesidades de validación.

Para garantizar que las direcciones de correo electrónico proporcionadas por el usuario estén libres de ruido e irregularidades, agregaremos un desinfectante a nuestro campo de correo electrónico como se ve en el fragmento anterior. El método normalizeEmail() ayuda a convertir los correos electrónicos ingresados ​​al formato estándar aprobado. Esto significa que si un usuario ingresa, por ejemplo, [correo electrónico protegido], se canonizará a [correo electrónico protegido].

Validator.js ofrece cierta flexibilidad, ya que esta opción se puede activar o desactivar, pero está activada de forma predeterminada. Hay un montón de opciones para la normalización que tal vez quieras revisar si planeas normalizar la entrada. Si desea leer más sobre otras funciones de validadores/sanitizadores, puede consultar Validator.js’ documentación oficial.

Probemos nuestro código enviando una solicitud con una contraseña no válida y un correo electrónico @googleemail.com, usando Postman o curl:

simple data validation

Hasta ahora, analizamos cómo podíamos validar las entradas entrantes para un punto final de inicio de sesión de muestra. Pasemos ahora al extremo de registro y cubramos tareas como reglas de validación personalizadas, mensajes de error, validación de esquemas y estandarización de mensajes de validación.

Reglas de validación personalizadas y mensajes de error con express-validator

Para comenzar, creemos nuestro punto final de registro de usuario agregando el siguiente fragmento de código a nuestro archivo index.js:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// index.js
...
app.post('/register', (req, res) => {
    // Validate incoming input
    res.status(200).json({
        success: true,
        message: 'Registration successful',
    });
});
...

Método personalizado()

Para asegurarnos de que nuestros usuarios ingresen nombres de usuario únicos durante el registro, no podemos usar los métodos estándar incluidos en los métodos de Validator.js, ya que no hay ningún método para verificarlo.

Tendremos que escribir un validador personalizado para esto, lo que se puede hacer usando el método custom(). El método custom() acepta una función, que además puede ser asíncrona. Si la función es asíncrona, querrá rechazar la promesa si falla la validación y especificar un mensaje personalizado. Si no, puede lanzar una excepción.

Comencemos por rechazar una promesa primero:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// index.js
...
app.post('/register',
    body("username").custom(value => {
        return User.find({
            username: value
        }).then(user => {
            if (user.length > 0) {
                // Custom error message and reject
                // the promise
                return Promise.reject('Username already in use');
            }
        });
    }),
    (req, res) => {
        // Validate incoming input
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({
                errors: errors.array()
            });
        }
        ...
    })

En el fragmento de código anterior, llamamos al método find() en el Modelo de usuario Esquema Mongoose para verificar si el nombre de usuario ingresó por el cliente ya existe en nuestra base de datos.

Si está presente, rechazamos la promesa con un mensaje que nos gustaría devolver al usuario.

Aunque MongoDB lo detectará automáticamente si el campo de nombre de usuario se marcó como único al especificar el esquema de la base de datos. Es recomendable manejar esto antes de que llegue a la base de datos para que nuestra aplicación no se bloquee prematuramente.

Alternativamente, puede lanzar una excepción como una forma de indicar una entrada no válida:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// index.js
...
    
app.post('/register',
        body("username").custom(value => {
            return User.find({
                username: value
            }).then(user => {
                if (user.length > 0) {
                    throw ("Username is taken!"); //custom error message
                }
            });
        }),
...

withMessage() Método

La segunda forma de implementar mensajes de error de validación personalizados es usando la cadena withMessage(). Puede poner varios validadores, seguidos de métodos encadenados withMessage() para especificar mensajes de error para cada validación:

1
2
3
4
5
body("parameter")
    .validator1()
    .withMessage('Message 1')
    .validator2()
    .withMessage('Message 2')

Apliquemos esto con métodos reales a nuestro ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// index.js
    
...
app.post('/register',
    body("password").isStrongPassword({
        minLength: 8,
        minLowercase: 1,
        minUppercase: 1,
        minNumbers: 1
    })
    .withMessage("Password must be greater than 8 and contain at least one uppercase letter, one lowercase letter, and one number"),
    (req, res) => {
        // Validate incoming input
    })
...

Hagamos otra solicitud, con una contraseña no válida y un nombre de usuario que ya está en uso:

custom validation rules and error messages

Validación de esquema con express-validator

La validación de esquemas ofrece un enfoque más limpio para validar datos. En lugar de llamar a numerosas funciones, especificamos las reglas de validación para cada campo y pasamos el esquema a una sola función de middleware llamada checkSchema().

En el fragmento a continuación, crearemos un esquema de validación para el punto final de registro de usuario:

 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// index.js
... 
const {body, checkSchema, validationResult} = require('express-validator');
const registrationSchema = {
    username: {
        custom: {
            options: value => {
                return User.find({
                    username: value
                }).then(user => {
                    if (user.length > 0) {
                        return Promise.reject('Username already in use')
                    }
                })
            }
        }
    },
    gender: {
        notEmpty: true,
        errorMessage: "Gender field cannot be empty"
    },
    password: {
        isStrongPassword: {
            minLength: 8,
            minLowercase: 1,
            minUppercase: 1,
            minNumbers: 1
        },
        errorMessage: "Password must be greater than 8 and contain at least one uppercase letter, one lowercase letter, and one number",
    },
    phone: {
        notEmpty: true,
        errorMessage: "Phone number cannot be empty"
    },
    email: {
        normalizeEmail: true,
        custom: {
            options: value => {
                return User.find({
                    email: value
                }).then(user => {
                    if (user.length > 0) {
                        return Promise.reject('Email address already taken')
                    }
                })
            }
        }
    }
}
...

Al especificar un esquema, podemos profundizar en campos de entrada específicos para aplicar validadores y desinfectantes, y es mucho más legible que encadenar muchos métodos con mensajes de validación como hemos visto en las secciones anteriores.

Ahora, podemos continuar y usar este checkSchema() para validar los datos en el registro:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
app.post('/signup', checkSchema(registrationSchema), (req, res) => {
    // Validate incoming input
    const errors = validationResult(req);

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

    res.status(200).json({
        success: true,
        message: 'Registration successful',
    });
})

Si solo necesita una pequeña cantidad de validaciones y quiere mantenerlo simple, puede usar métodos. Si tiene una gran cantidad de validaciones por hacer, será más legible si usa la validación de esquema.

Estandarización de respuestas de validación con express-validator

express-validator hace posible estandarizar las respuestas de error de validación. Esto significa que puede crear sus funciones de middleware para ejecutar validaciones y manejar errores de validación.

Un ejemplo de cómo se puede hacer esto es creando una función validate() que aceptará todos nuestros validadores y los ejecutará en paralelo usando Promise.all():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// index.js
const validate = validations => {
    return async (req, res, next) => {
        await Promise.all(validations.map(validation => validation.run(req)));

        const errors = validationResult(req);
        if (errors.isEmpty()) {
            return next();
        }

        res.status(400).json({
            errors: errors.array()
        });
    };
};

Ahora que se ha creado nuestra función de validación, podemos reutilizarla en varias rutas. Apliquémoslo a nuestras rutas de inicio de sesión y registro:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// index.js
...
app.post('/login', validate([
        body('email').isEmail().normalizeEmail(),
        body('password').isLength({
            min: 12
        })
    ]),
    (req, res) => {
        // Process data
        res.status(200).json({
            success: true,
            message: 'Login successful',
        })
    });

app.post('/register', validate(checkSchema(registrationSchema)), (req, res) => {
    // Process data
    res.status(200).json({
        success: true,
        message: 'Registration successful',
    });
});
...

Como se ve en el fragmento anterior, el uso de un middleware de validación personalizado que ejecuta todos nuestros validadores y desinfectantes no solo nos brinda un aumento de rendimiento con la llamada Promise.all(), sino que también podemos mejorar la legibilidad del código. Esto resultará útil cuando necesitemos validar muchos campos de formulario.

Conclusión

En este artículo, hemos repasado el uso básico y más avanzado de express-validator, una gran biblioteca liviana que envuelve la conocida biblioteca validator.js.