Express

Express

Tutorial Express: Métodos PUT y PATCH

Aprende a usar métodos PUT y PATCH en Express para actualizaciones completas y parciales de recursos con validación y manejo de errores.

Aprende Express y certifícate

Diferencias PUT vs PATCH

Los métodos PUT y PATCH representan dos enfoques distintos para actualizar recursos en una API REST, cada uno con características específicas que determinan cuándo y cómo utilizarlos en Express.

Naturaleza de la actualización

El método PUT está diseñado para realizar actualizaciones completas de un recurso. Cuando utilizas PUT, el servidor espera recibir una representación completa del recurso que reemplazará totalmente la versión existente. Si omites algún campo en la petición PUT, ese campo debería eliminarse o establecerse a su valor por defecto.

app.put('/usuarios/:id', (req, res) => {
  const { id } = req.params;
  const datosCompletos = req.body;
  
  // PUT espera todos los campos del usuario
  const usuarioActualizado = {
    nombre: datosCompletos.nombre,
    email: datosCompletos.email,
    edad: datosCompletos.edad,
    telefono: datosCompletos.telefono
  };
  
  // Reemplaza completamente el recurso
  usuarios[id] = usuarioActualizado;
  res.json(usuarioActualizado);
});

Por el contrario, PATCH permite actualizaciones parciales. Solo necesitas enviar los campos que deseas modificar, manteniendo intactos el resto de atributos del recurso.

app.patch('/usuarios/:id', (req, res) => {
  const { id } = req.params;
  const cambiosParciales = req.body;
  
  // PATCH solo modifica los campos enviados
  const usuarioExistente = usuarios[id];
  const usuarioActualizado = {
    ...usuarioExistente,
    ...cambiosParciales
  };
  
  usuarios[id] = usuarioActualizado;
  res.json(usuarioActualizado);
});

Idempotencia y comportamiento

Ambos métodos son idempotentes, lo que significa que realizar la misma operación múltiples veces produce el mismo resultado. Sin embargo, su implementación práctica difiere significativamente.

PUT mantiene la idempotencia de forma más estricta porque siempre reemplaza el recurso completo:

// Primera llamada PUT
PUT /productos/123
{
  "nombre": "Laptop Gaming",
  "precio": 1200,
  "categoria": "Electrónicos"
}

// Segunda llamada PUT idéntica produce el mismo resultado
PUT /productos/123
{
  "nombre": "Laptop Gaming",
  "precio": 1200,
  "categoria": "Electrónicos"
}

PATCH puede ser idempotente dependiendo de la implementación, especialmente cuando se utilizan operaciones de asignación directa:

app.patch('/productos/:id', (req, res) => {
  const { id } = req.params;
  const producto = productos[id];
  
  // Operación idempotente: asignación directa
  if (req.body.precio) {
    producto.precio = req.body.precio;
  }
  
  // Operación no idempotente: incremento
  if (req.body.incrementarStock) {
    producto.stock += req.body.incrementarStock;
  }
  
  res.json(producto);
});

Validación y estructura de datos

La validación en PUT debe ser más estricta porque requiere una representación completa del recurso:

app.put('/articulos/:id', (req, res) => {
  const camposRequeridos = ['titulo', 'contenido', 'autor', 'fechaPublicacion'];
  
  // PUT requiere validación completa
  const camposFaltantes = camposRequeridos.filter(campo => !req.body[campo]);
  
  if (camposFaltantes.length > 0) {
    return res.status(400).json({
      error: `Campos requeridos faltantes: ${camposFaltantes.join(', ')}`
    });
  }
  
  // Proceder con la actualización completa
  articulos[req.params.id] = req.body;
  res.json(req.body);
});

PATCH permite una validación más flexible, enfocándose únicamente en los campos proporcionados:

app.patch('/articulos/:id', (req, res) => {
  const camposPermitidos = ['titulo', 'contenido', 'tags'];
  const actualizacion = {};
  
  // PATCH valida solo los campos enviados
  for (const campo of camposPermitidos) {
    if (req.body[campo] !== undefined) {
      actualizacion[campo] = req.body[campo];
    }
  }
  
  if (Object.keys(actualizacion).length === 0) {
    return res.status(400).json({
      error: 'No se proporcionaron campos válidos para actualizar'
    });
  }
  
  Object.assign(articulos[req.params.id], actualizacion);
  res.json(articulos[req.params.id]);
});

Casos de uso prácticos

PUT resulta ideal cuando necesitas reemplazar completamente un recurso o cuando trabajas con formularios que capturan todos los datos del recurso:

// Actualización completa de perfil de usuario
app.put('/perfiles/:userId', (req, res) => {
  const perfilCompleto = {
    nombre: req.body.nombre,
    apellido: req.body.apellido,
    email: req.body.email,
    biografia: req.body.biografia || '',
    configuracionPrivacidad: req.body.configuracionPrivacidad || {}
  };
  
  perfiles[req.params.userId] = perfilCompleto;
  res.json(perfilCompleto);
});

PATCH es más apropiado para actualizaciones específicas, como cambiar el estado de un pedido o actualizar campos individuales:

// Actualización parcial del estado de pedido
app.patch('/pedidos/:id/estado', (req, res) => {
  const pedido = pedidos[req.params.id];
  
  if (req.body.estado) {
    pedido.estado = req.body.estado;
    pedido.fechaActualizacion = new Date();
  }
  
  res.json(pedido);
});

La elección entre PUT y PATCH depende fundamentalmente de si necesitas una actualización completa o parcial del recurso, así como de los requisitos específicos de tu aplicación en términos de validación y comportamiento esperado por los clientes de tu API.

Actualización de recursos

La actualización de recursos en Express requiere una implementación cuidadosa que considere tanto la integridad de los datos como la experiencia del usuario. Los métodos PUT y PATCH proporcionan diferentes estrategias para modificar recursos existentes, cada una con patrones específicos de implementación.

Manejo de recursos inexistentes

Antes de actualizar cualquier recurso, es fundamental verificar su existencia. Esta validación debe realizarse de forma consistente en ambos métodos:

const verificarRecursoExiste = (coleccion, id) => {
  return coleccion[id] !== undefined;
};

app.put('/productos/:id', (req, res) => {
  const { id } = req.params;
  
  if (!verificarRecursoExiste(productos, id)) {
    return res.status(404).json({
      error: 'Producto no encontrado',
      id: id
    });
  }
  
  // Proceder con la actualización
  productos[id] = req.body;
  res.json(productos[id]);
});

Preservación de metadatos

Durante las actualizaciones, ciertos campos del sistema deben preservarse o actualizarse automáticamente. Estos metadatos proporcionan trazabilidad y control de versiones:

app.patch('/documentos/:id', (req, res) => {
  const { id } = req.params;
  const documento = documentos[id];
  
  // Preservar metadatos del sistema
  const documentoActualizado = {
    ...documento,
    ...req.body,
    id: documento.id, // ID inmutable
    fechaCreacion: documento.fechaCreacion, // Preservar fecha original
    fechaModificacion: new Date(), // Actualizar timestamp
    version: documento.version + 1 // Incrementar versión
  };
  
  documentos[id] = documentoActualizado;
  res.json(documentoActualizado);
});

Validación de tipos y formatos

La validación de datos debe adaptarse al tipo de actualización. PUT requiere validación completa, mientras que PATCH valida solo los campos proporcionados:

const validarEmail = (email) => {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
};

app.patch('/usuarios/:id', (req, res) => {
  const { id } = req.params;
  const usuario = usuarios[id];
  
  // Validar solo campos presentes en la actualización
  if (req.body.email && !validarEmail(req.body.email)) {
    return res.status(400).json({
      error: 'Formato de email inválido'
    });
  }
  
  if (req.body.edad && (req.body.edad < 0 || req.body.edad > 120)) {
    return res.status(400).json({
      error: 'Edad debe estar entre 0 y 120 años'
    });
  }
  
  // Aplicar cambios validados
  Object.assign(usuario, req.body);
  res.json(usuario);
});

Manejo de relaciones entre recursos

Cuando un recurso tiene relaciones con otros recursos, las actualizaciones deben mantener la integridad referencial:

app.put('/proyectos/:id', (req, res) => {
  const { id } = req.params;
  const proyecto = proyectos[id];
  
  // Validar que los usuarios asignados existen
  if (req.body.equipoAsignado) {
    const usuariosInvalidos = req.body.equipoAsignado.filter(
      userId => !usuarios[userId]
    );
    
    if (usuariosInvalidos.length > 0) {
      return res.status(400).json({
        error: 'Usuarios no encontrados',
        usuariosInvalidos
      });
    }
  }
  
  // Actualizar proyecto manteniendo integridad
  proyectos[id] = {
    ...req.body,
    id: proyecto.id,
    fechaCreacion: proyecto.fechaCreacion,
    fechaModificacion: new Date()
  };
  
  res.json(proyectos[id]);
});

Respuestas diferenciadas por tipo de actualización

Las respuestas HTTP deben reflejar el tipo de operación realizada y proporcionar información útil al cliente:

app.put('/configuraciones/:id', (req, res) => {
  const { id } = req.params;
  const configuracionAnterior = { ...configuraciones[id] };
  
  // Reemplazo completo
  configuraciones[id] = {
    ...req.body,
    id,
    fechaActualizacion: new Date()
  };
  
  res.json({
    mensaje: 'Configuración reemplazada completamente',
    anterior: configuracionAnterior,
    actual: configuraciones[id],
    cambiosRealizados: Object.keys(req.body).length
  });
});

app.patch('/configuraciones/:id', (req, res) => {
  const { id } = req.params;
  const configuracion = configuraciones[id];
  const camposModificados = Object.keys(req.body);
  
  // Actualización parcial
  Object.assign(configuracion, req.body);
  configuracion.fechaActualizacion = new Date();
  
  res.json({
    mensaje: 'Configuración actualizada parcialmente',
    recurso: configuracion,
    camposModificados,
    totalCampos: camposModificados.length
  });
});

Optimización de actualizaciones masivas

Para actualizaciones que afectan múltiples campos o requieren procesamiento adicional, es importante optimizar las operaciones:

app.patch('/inventario/:id', (req, res) => {
  const { id } = req.params;
  const producto = inventario[id];
  const actualizaciones = req.body;
  
  // Procesar actualizaciones en lotes
  const cambiosAplicados = {};
  
  // Actualizar stock si se proporciona
  if (actualizaciones.ajusteStock !== undefined) {
    producto.stock += actualizaciones.ajusteStock;
    cambiosAplicados.stockAnterior = producto.stock - actualizaciones.ajusteStock;
    cambiosAplicados.stockActual = producto.stock;
  }
  
  // Actualizar precio con historial
  if (actualizaciones.precio !== undefined) {
    if (!producto.historialPrecios) {
      producto.historialPrecios = [];
    }
    
    producto.historialPrecios.push({
      precio: producto.precio,
      fecha: new Date()
    });
    
    producto.precio = actualizaciones.precio;
    cambiosAplicados.precioActualizado = true;
  }
  
  // Actualizar otros campos directamente
  const camposDirectos = ['nombre', 'descripcion', 'categoria'];
  camposDirectos.forEach(campo => {
    if (actualizaciones[campo] !== undefined) {
      producto[campo] = actualizaciones[campo];
      cambiosAplicados[campo] = true;
    }
  });
  
  res.json({
    producto,
    cambiosAplicados,
    timestamp: new Date()
  });
});

La implementación efectiva de actualizaciones de recursos en Express requiere considerar la consistencia de datos, la validación apropiada y el manejo de errores, asegurando que cada operación mantenga la integridad del sistema mientras proporciona retroalimentación clara al cliente sobre los cambios realizados.

Aprende Express online

Otras lecciones de Express

Accede a todas las lecciones de Express y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Express y certifícate

Ejercicios de programación de Express

Evalúa tus conocimientos de esta lección Métodos PUT y PATCH con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.