Validacion de entradas limpia y simple en Express.js
Backend

Validacion de entradas limpia y simple en Express.js

6 October 2026 · 8 min de lectura
Home / Backend / Validacion de entradas limpia y simple en Express.js
Categorías:

Introduccion a la validacion de entradas en Express

La validacion de entradas es una de las capas de defensa mas importantes en cualquier aplicacion web. El servidor no debe confiar nunca en los datos que llegan desde el cliente. Un usuario puede desactivar JavaScript, modificar peticiones con herramientas de desarrollo o interceptar el trafico. Por eso la validacion del lado del servidor resulta indispensable para mantener la integridad de los datos y reducir la superficie de ataque.

En aplicaciones construidas con Express.js es frecuente encontrar controladores llenos de comprobaciones manuales del tipo if (!req.body.email) o if (!isValidEmail(req.body.email)). Este enfoque mezcla la logica de negocio con la de validacion, dificulta el mantenimiento y genera codigo repetitivo. Una solucion limpia consiste en extraer las reglas de validacion a middleware reutilizable que se ejecuta antes del controlador.

Este articulo muestra como implementar una validacion clara y mantenible utilizando express-validator, explica las mejores practicas actuales y menciona alternativas como Joi y Zod para proyectos con necesidades distintas. El objetivo es separar la validacion de la logica de negocio y devolver errores comprensibles al cliente.

Por que la validacion en el servidor es obligatoria

La validacion en el navegador mejora la experiencia de usuario, pero no ofrece ninguna garantia de seguridad. Un atacante puede enviar peticiones directamente al endpoint omitiendo por completo el formulario. Ademas, los datos pueden alterarse en transito si no se utiliza HTTPS correctamente. Por estas razones el servidor debe volver a validar todo lo que recibe: cuerpo de la peticion, parametros de ruta, query strings y, cuando corresponda, cabeceras.

Una validacion deficiente puede permitir inyecciones, desbordamientos de datos, valores fuera de rango o tipos incorrectos que provoquen errores en capas posteriores o comportamientos inesperados. Validar de forma temprana y devolver respuestas de error estructuradas evita que datos invalidos lleguen a la base de datos o a la logica de negocio.

Instalacion y configuracion basica

El paquete express-validator se instala con npm y se utiliza como middleware en las rutas. En versiones recientes ya no es necesario registrar un middleware global de la forma antigua; se importan las funciones necesarias y se aplican directamente en cada ruta.

npm install express-validator

En el archivo principal de la aplicacion se configura el parser de JSON y se montan las rutas:

const express = require("express");
const app = express();

app.use(express.json());

const userRoutes = require("./routes/user");
app.use("/api/users", userRoutes);

A partir de aqui las reglas de validacion se definen como arrays de middleware que se ejecutan antes del manejador de la ruta.

Estructura de una ruta con validacion

Una forma limpia de organizar el codigo consiste en separar las reglas de validacion del controlador. El archivo de rutas solo declara el orden de los middlewares:

const express = require("express");
const router = express.Router();
const userController = require("../controllers/user");
const { createUserValidation } = require("../validators/user");

router.post("/", createUserValidation, userController.createUser);

module.exports = router;

Las reglas viven en un modulo de validadores y el controlador se centra exclusivamente en la logica de negocio una vez que los datos han sido validados.

Definicion de reglas con la cadena de validacion

express-validator ofrece funciones como body, param, query y header para indicar de donde se toma el valor. Cada una devuelve una cadena sobre la que se pueden encadenar validadores y sanitizadores.

const { body } = require("express-validator");

const createUserValidation = [
    body("userName")
        .exists({ checkFalsy: true })
        .withMessage("El nombre de usuario es obligatorio")
        .isString()
        .trim()
        .isLength({ min: 3, max: 30 })
        .withMessage("El nombre debe tener entre 3 y 30 caracteres"),

    body("email")
        .exists()
        .withMessage("El email es obligatorio")
        .isEmail()
        .withMessage("Debe proporcionar un email valido")
        .normalizeEmail(),

    body("phone")
        .optional()
        .isMobilePhone("any")
        .withMessage("Numero de telefono no valido"),

    body("status")
        .optional()
        .isIn(["enabled", "disabled"])
        .withMessage("El estado solo puede ser enabled o disabled"),
];

module.exports = { createUserValidation };

exists comprueba que el campo este presente. optional permite que el campo falte sin generar error. isEmail, isLength, isIn y el resto de validadores provienen de la libreria validator.js y cubren la mayoria de los casos habituales. withMessage personaliza el texto que se devolvera al cliente.

Obtencion y manejo de los resultados de validacion

En el controlador se utiliza validationResult para inspeccionar si hubo errores. Si el resultado no esta vacio se responde con el codigo 422 y la lista de errores, evitando que se ejecute la logica de negocio.

const { validationResult } = require("express-validator");
const User = require("../models/user");

exports.createUser = async (req, res, next) => {
    try {
        const errors = validationResult(req);

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

        const { userName, email, phone, status } = req.body;

        const user = await User.create({
            userName,
            email,
            phone,
            status,
        });

        res.status(201).json(user);
    } catch (err) {
        next(err);
    }
};

El metodo array() devuelve un listado de objetos con propiedades como msg, param, location y value. Esta estructura permite al cliente mostrar mensajes de error asociados a cada campo del formulario.

Sanitizacion de datos

Ademas de validar, es recomendable sanitizar los valores para eliminar espacios innecesarios, normalizar formatos o escapar caracteres peligrosos. express-validator permite encadenar sanitizadores directamente en la misma cadena.

(body("userName").trim().escape().isLength({ min: 3 }),
    body("email").normalizeEmail().isEmail());

trim elimina espacios al inicio y al final. escape convierte caracteres HTML potencialmente peligrosos. normalizeEmail unifica el formato del correo. Estas operaciones reducen la probabilidad de problemas de presentacion o de inyeccion cuando los datos se reutilizan en plantillas o consultas.

Validadores personalizados

Cuando las reglas predefinidas no bastan se puede utilizar custom. El callback recibe el valor y puede realizar comprobaciones sincronas o asincronas. Si la validacion falla se lanza un Error o se rechaza una promesa.

body("email")
    .isEmail()
    .custom(async (value) => {
        const existing = await User.findOne({ email: value });
        if (existing) {
            throw new Error("El email ya esta registrado");
        }
        return true;
    });

Este patron resulta util para comprobar unicidad en base de datos, validar contraseñas contra politicas internas o verificar relaciones entre campos.

Validacion de parametros de ruta y query

No solo el cuerpo de la peticion necesita validacion. Los identificadores en la URL y los parametros de consulta tambien deben comprobarse.

const { param, query } = require("express-validator");

const getUserValidation = [
    param("id").isMongoId().withMessage("Identificador de usuario no valido"),
];

const listUsersValidation = [
    query("page").optional().isInt({ min: 1 }).toInt(),
    query("limit").optional().isInt({ min: 1, max: 100 }).toInt(),
];

toInt convierte el valor a numero despues de validarlo, de modo que el controlador recibe ya el tipo correcto.

Extraccion de la logica de respuesta de errores

Para evitar repetir el bloque de comprobacion de errores en cada controlador se puede crear un middleware generico:

const { validationResult } = require("express-validator");

function handleValidationErrors(req, res, next) {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
        return res.status(422).json({ errors: errors.array() });
    }
    next();
}

module.exports = handleValidationErrors;

La ruta queda entonces mas declarativa:

router.post(
    "/",
    createUserValidation,
    handleValidationErrors,
    userController.createUser
);

Este enfoque mantiene los controladores enfocados en la logica de negocio y centraliza el formato de respuesta de errores.

Alternativas modernas: Joi y Zod

Aunque express-validator se integra de forma natural con Express, existen otras librerias ampliamente utilizadas. Joi permite definir esquemas completos de objetos y es muy flexible para reglas complejas y condicionales. Zod destaca por su integracion con TypeScript y por la inferencia automatica de tipos a partir del esquema.

Ejemplo basico con Joi:

const Joi = require("joi");

const userSchema = Joi.object({
    userName: Joi.string().alphanum().min(3).max(30).required(),
    email: Joi.string().email().required(),
    phone: Joi.string().optional(),
    status: Joi.string().valid("enabled", "disabled").optional(),
});

function validateBody(schema) {
    return (req, res, next) => {
        const { error, value } = schema.validate(req.body, {
            abortEarly: false,
            stripUnknown: true,
        });
        if (error) {
            return res.status(422).json({
                errors: error.details.map((d) => ({
                    msg: d.message,
                    param: d.path.join("."),
                })),
            });
        }
        req.body = value;
        next();
    };
}

La eleccion entre express-validator, Joi y Zod depende del contexto: express-validator es comodo cuando se quiere validacion declarativa dentro de las rutas de Express; Joi ofrece gran potencia para esquemas reutilizables; Zod brilla en proyectos TypeScript donde se desea una unica fuente de verdad para tipos y validacion.

Buenas practicas de seguridad y diseno

Validar siempre en el servidor, incluso si existe validacion en el cliente. Devolver todos los errores posibles en una sola respuesta mejora la experiencia de usuario. Sanitizar los datos despues de validarlos reduce riesgos de inyeccion. Evitar confiar en el orden de los middlewares: colocar la validacion justo antes del controlador que consume los datos.

Separar las reglas de validacion en modulos propios facilita las pruebas unitarias y la reutilizacion entre rutas. Utilizar codigos de estado HTTP adecuados (422 para errores de validacion, 400 para peticiones mal formadas) ayuda a los clientes a interpretar la respuesta. Finalmente, no exponer mensajes internos del sistema en los errores devueltos al usuario.

Pruebas de la capa de validacion

Las reglas de validacion pueden probarse de forma aislada enviando objetos simulados de request. Tambien es util escribir pruebas de integracion que verifiquen que una ruta rechaza cargas invalidas y acepta las validas. Comprobar tanto el codigo de estado como la estructura del cuerpo de error garantiza que el contrato de la API se mantiene estable.

Conclusion

Una validacion limpia y temprana es fundamental para la robustez y la seguridad de una API Express. express-validator permite definir reglas de forma declarativa, sanitizar valores y devolver errores estructurados sin contaminar la logica de negocio. Combinado con middlewares reutilizables y una clara separacion de responsabilidades, el codigo resultante es mas legible, mas facil de mantener y menos propenso a errores.

En proyectos mas grandes o con TypeScript, Joi y Zod ofrecen alternativas potentes que merecen evaluarse. Independientemente de la libreria elegida, el principio sigue siendo el mismo: no confiar en el cliente, validar en el servidor y mantener la validacion separada de la logica de aplicacion. Aplicar estas practicas reduce vulnerabilidades y mejora la calidad general del servicio.