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ícateDiferencias 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.
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.
Introducción A Expressjs
Introducción Y Entorno
Instalación De Express
Introducción Y Entorno
Estados Http
Routing
Métodos Delete
Routing
Parámetros Y Query Strings
Routing
Métodos Get
Routing
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.