¿Cómo se mapean las operaciones CRUD a los verbos HTTP y qué significa que un verbo sea idempotente?
Ver respuesta — intenta responderla en voz alta primero
Create = POST, Read = GET, Update = PUT o PATCH, Delete = DELETE. GET, PUT y DELETE son idempotentes (repetirlos deja el sistema en el mismo estado); POST no lo es (repetirlo crea recursos nuevos). PUT reemplaza el recurso completo, mientras que PATCH actualiza solo una parte.
CRUD son las cuatro operaciones básicas sobre datos: Crear, Leer, Actualizar y Borrar. En una API REST cada una tiene su verbo HTTP:
| CRUD | Verbo HTTP | Ejemplo | Idempotente |
|---|---|---|---|
| Create (crear) | POST | POST /usuarios |
No |
| Read (leer) | GET | GET /usuarios/5 |
Sí |
| Update (actualizar) | PUT / PATCH | PUT /usuarios/5 |
Sí |
| Delete (borrar) | DELETE | DELETE /usuarios/5 |
Sí |
Idempotencia
Una operación es idempotente si ejecutarla una vez o muchas veces deja el sistema en el mismo estado final.
- GET es idempotente: leer 100 veces no cambia nada.
- PUT es idempotente: si mando el mismo recurso completo 5 veces, el resultado final es el mismo que mandarlo 1 vez.
- DELETE es idempotente: borrar un recurso una o varias veces deja el sistema igual (el recurso ya no está).
- POST NO es idempotente: cada POST a una colección normalmente crea un recurso nuevo. Hacerlo 3 veces crea 3 recursos.
Esto importa mucho para reintentos: si una petición falla por red, un cliente puede reintentar con seguridad un GET, PUT o DELETE, pero repetir un POST puede duplicar datos.
PUT vs PATCH
- PUT reemplaza el recurso completo. Debes enviar el objeto entero; los campos que no mandes se consideran ausentes.
- PATCH actualiza parcialmente. Envías solo los campos que quieres cambiar; el resto se queda como estaba.
Ejemplo: un usuario { id: 5, nombre: "Ana", email: "ana@mail.com" }.
- Con
PATCH /usuarios/5y body{ "email": "nuevo@mail.com" }, el nombre sigue siendo "Ana". - Con
PUT /usuarios/5y ese mismo body sinnombre, corres el riesgo de borrar el nombre, porque PUT representa el recurso completo.
const express = require('express');
const app = express();
app.use(express.json());
let usuarios = [{ id: 5, nombre: 'Ana', email: 'ana@mail.com' }];
// READ (GET) - idempotente
app.get('/usuarios/:id', (req, res) => {
const usuario = usuarios.find((u) => u.id === Number(req.params.id));
if (!usuario) return res.status(404).json({ error: 'No encontrado' });
res.json(usuario);
});
// CREATE (POST) - NO idempotente: cada llamada crea uno nuevo
app.post('/usuarios', (req, res) => {
const nuevo = { id: Date.now(), ...req.body };
usuarios.push(nuevo);
res.status(201).json(nuevo);
});
// UPDATE completo (PUT) - reemplaza todo el recurso
app.put('/usuarios/:id', (req, res) => {
const id = Number(req.params.id);
const indice = usuarios.findIndex((u) => u.id === id);
if (indice === -1) return res.status(404).json({ error: 'No encontrado' });
// Reemplazamos el recurso completo (conservando el id)
usuarios[indice] = { id, nombre: req.body.nombre, email: req.body.email };
res.json(usuarios[indice]);
});
// UPDATE parcial (PATCH) - solo cambia los campos enviados
app.patch('/usuarios/:id', (req, res) => {
const usuario = usuarios.find((u) => u.id === Number(req.params.id));
if (!usuario) return res.status(404).json({ error: 'No encontrado' });
// Mezclamos lo existente con lo nuevo; lo que no venga se conserva
Object.assign(usuario, req.body);
res.json(usuario);
});
// DELETE - idempotente
app.delete('/usuarios/:id', (req, res) => {
const indice = usuarios.findIndex((u) => u.id === Number(req.params.id));
if (indice === -1) return res.status(404).json({ error: 'No encontrado' });
usuarios.splice(indice, 1);
res.status(204).send();
});
app.listen(3000);
# PATCH: solo cambio el email, el nombre se conserva
curl -X PATCH http://localhost:3000/usuarios/5 \
-H "Content-Type: application/json" \
-d '{"email":"nuevo@mail.com"}'
# PUT: envío el recurso completo
curl -X PUT http://localhost:3000/usuarios/5 \
-H "Content-Type: application/json" \
-d '{"nombre":"Ana","email":"ana@mail.com"}'
Usar PUT como si fuera PATCH enviando solo un campo. Como PUT representa el recurso completo, los campos ausentes pueden terminar borrados o puestos a null. Si solo quieres cambiar un dato, usa PATCH.
Otro error es usar GET para modificar datos (por ejemplo GET /usuarios/5/borrar). GET debe ser seguro e idempotente: los navegadores, cachés y crawlers pueden repetirlo libremente, y eso borraría datos sin querer.
"El mapeo típico es Create con POST, Read con GET, Update con PUT o PATCH y Delete con DELETE. Un concepto clave es la idempotencia: una operación es idempotente si repetirla deja el sistema en el mismo estado. GET, PUT y DELETE lo son, pero POST no, porque cada POST suele crear un recurso nuevo. Eso me importa sobre todo para los reintentos: un GET o un DELETE los puedo reintentar sin miedo, pero repetir un POST podría duplicar datos. Sobre PUT y PATCH: PUT reemplaza el recurso completo, así que debo enviar el objeto entero; PATCH actualiza solo los campos que mando y deja el resto intacto. Por eso, si solo quiero cambiar un campo, uso PATCH."
Un cliente hace DELETE /pedidos/42 y la respuesta se pierde por un problema de red, así que reintenta la misma petición. La segunda vez el pedido ya no existe. ¿Es correcto este comportamiento respecto a la idempotencia? ¿Qué código devolverías?
Ver respuesta
Sí, es correcto y consistente con la idempotencia: el estado final del sistema es el mismo tanto si el DELETE se ejecuta una vez como si se reintenta (el pedido 42 no existe).
En la práctica, la primera llamada podría devolver 204 No Content (borrado con éxito) y la segunda 404 Not Found (ya no existe). El código puede diferir, pero el estado del sistema no cambia con los reintentos, que es lo que define la idempotencia.