Validaciones con middleware en Express

En esta clase metemos una mejora muy profesional a nuestra API - validar lo que entra antes de crear o actualizar recursos. La idea es simple: si el cliente manda datos inválidos, no llegamos al controlador. Cortamos antes con un 400 y un mensaje útil.

¿Dónde validamos?

La validación la vamos a aplicar en las rutas, usando un middleware que se ejecuta antes del handler final. Así, cada endpoint decide qué validación necesita y el controlador se centra solo en la lógica de negocio.

Dos casos típicos: crear vs actualizar

  • POST: validación completa (todos los campos requeridos)
  • PATCH: validación parcial (solo lo que venga en el body)

Para eso reutilizamos dos esquemas:

  • validateJob para creación
  • validatePartialJob para actualización parcial

Middleware: validateCreate

Creamos una función validateCreate con la firma típica req, res, next, y validamos con safeParse para saber si ha ido bien sin tirar excepciones.

// routes/jobs.js (o en middlewares/validateJob.js si lo quieres separar)
import { validateJob } from '../schemas/jobs.js'

export function validateCreate(req, res, next) {
  const result = validateJob(req.body)

  if (result.success) {
    // Opcional pero muy útil: dejamos el body "limpio" y tipado por el schema
    req.body = result.data
    return next()
  }

  return res.status(400).json({
    message: 'Invalid request',
    errors: result.error.errors,
  })
}

Esto hace dos cosas clave:

  • Si es válido - next() y seguimos al controlador.
  • Si no es válido - 400 + lista de errores detallada.

Middleware: validateUpdate

Para el PATCH, lo mismo, pero con el schema parcial:

import { validatePartialJob } from '../schemas/jobs.js'

export function validateUpdate(req, res, next) {
  const result = validatePartialJob(req.body)

  if (result.success) {
    req.body = result.data
    return next()
  }

  return res.status(400).json({
    message: 'Invalid request',
    errors: result.error.errors,
  })
}

Conectándolo en las rutas

Ahora ponemos el middleware justo antes del handler del endpoint:

import { Router } from 'express'
import { createJob, updateJob } from '../controllers/jobs.js'
import { validateCreate, validateUpdate } from '../middlewares/jobsValidation.js'

const router = Router()

router.post('/', validateCreate, createJob)
router.patch('/:id', validateUpdate, updateJob)

export default router

Así queda clarísimo el flujo:

  1. entra request
  2. valida
  3. si pasa, ejecuta el controlador
  4. si no pasa, responde con error

Bonus: validación + transformación

Una idea interesante es que el middleware no solo valide, también puede transformar datos para normalizarlos. Por ejemplo:

  • pasar technologies a lowercase
  • mapear campos
  • limpiar strings

Y como reasignamos req.body = result.data, el controlador recibe los datos ya listos para trabajar.

Probándolo rápido

La validación se nota al instante:

  • si quitas un campo obligatorio como title, devuelve Invalid request y no crea el recurso
  • si mandas un tipo incorrecto (por ejemplo un número donde esperaba texto), también falla

Lo que hemos aprendido

  • Dónde colocar las validaciones - en rutas con middleware
  • Cómo diferenciar validación total (POST) y parcial (PATCH)
  • Cómo usar safeParse para obtener success, data y errors
  • Cómo devolver 400 con mensajes útiles
  • Por qué esto también se tiene que testear en la API