Te llega una tarea nueva : "el cliente debe poder actualizar su email". Abres el controlador, lo miras, y piensas, ¿POST, PUT o PATCH?. Los tres son métodos HTTP con semánticas diferentes, y mezclarlos puede tener consecuencias el día de mañana. La mayoría de APIs que vemos en producción usan POST para todo aquello que implique insertar datos, o utilizan PUT y PATCH sin mucho criterio.
POST: crear, no actualizar
POST es el método de creación. Cuando lo usas, le estás diciendo al servidor "crea algo nuevo con esto". La respuesta habitual es un 201 Created con la URL del recurso recién creado en el header Location.
Lo que distingue a POST de los demás es que no es idempotente. Es decir, si envías la misma petición dos veces, tendrás dos recursos creados. Esto es correcto. El problema viene cuando utilizamos POST para actualizar el recurso, porque "total, funciona". Funciona, sí, pero estás rompiendo la semántica del protocolo y renunciando a ventajas concretas.
POST /usuarios
{ "nombre": "Ana", "email": "ana@ejemplo.com" }
→ 201 Created
Location: /usuarios/42
PUT: sustituir, no actualizar
PUT es sustitución completa. Es decir, cuando envías un PUT a, por ejemplo, /usuarios/42, le estás diciendo al servidor "el recurso en esta URL es ahora exactamente esto". No una parte del objeto o del recurso sino todo.
Esto tiene una implicación que no siempre se entiende bien, si tu objeto tiene 20 campos y envías un PUT con solo 5, los otros 15 campos quedan a criterio de como se haya implementado el servidor. En algunas implementaciones se ponen a null o se eliminan directamente. Ese sería el comportamiento correcto siguiendo la semántica de PUT, y es una fuente importante de errores cuando el programador cree que solo está haciendo una actualización parcial.
PUT sí es idempotente. Puedes enviar la misma petición diez veces y el resultado es idéntico a enviarla una. Eso tiene valor real: los clientes pueden reintentar con seguridad ante un error de red sin preocuparse de efectos secundarios.
PUT /usuarios/42
{ "nombre": "Ana", "email": "ana2@ejemplo.com", "rol": "admin", ... }
→ 200 OK
PATCH: modificar lo que necesitas
De aquí nace la idea del PATCH, cubrir el caso que PUT no maneja bien, las actualizaciones parciales. Solo quieres cambiar el email, con PATCH envías exactamente lo que quieres cambiar. Y tu servidor aplica la modificación sobre el estado actual del recurso.
PATCH /usuarios/42
{ "email": "ana2@ejemplo.com" }
→ 200 OK
Simple. Pero tiene un matiz importante, no garantiza idempotencia. Depende de cómo esté implementado. Si el servidor aplica el cambio de forma determinista sobre el estado de un campo, es determinista, pero si la operación es incrementar el contador en 1, no lo es. Con lo que la idempotencia es responsabilidad del diseñador de la API y no del protocolo.
Para casos más complejos existe el formato JSON Patch (RFC 6902), que describe las operaciones de modificación de forma explícita: add, remove, replace, move, copy, test. Permite expresar cambios estructurales sobre un documento JSON sin ambigüedad. En APIs públicas o con contratos estrictos vale la pena conocerlo; en APIs internas, enviar los campos modificados directamente es suficiente en la mayoría de casos.
Por qué la idempotencia importa más de lo que parece
La idempotencia no es solo un detalle académico. Tiene consecuencias directas y graves en cómo diseñas los reintentos, en cómo se comportan los proxies o las caches intermedias, y si es seguro para un cliente volver a intentar una petición fallida.
Si un cliente hace un PUT, sabe que puede reintentarlo tantas veces como quiera sin riesgo alguno si pierde la conexión por ejemplo. En cambio si hace un POST, un reintento puede crear un duplicado. Esa información cambia cómo escribes código en ambos extremos.
Diseñas APIs con idempotencia en mente nos puede ahorrar bugs difíciles de reproducir o diagnosticar.
Un criterio práctico para decidir
Después de todo esto, la regla es más simple de lo que parece:
- ¿Estás creando un recurso nuevo? → POST
- ¿Estás reemplazando un recurso completo y explícitamente? → PUT
- ¿Estás modificando campos concretos de un recurso existente? → PATCH
Una de las trampas más comunes es usar PUT en vez de PATCH porque "estoy enviando el objeto completo desde el frontend". Técnicamente funciona, pero introduces una dependencia implícita, el frontend debe conocer el estado completo del objeto en todo momento para modificar campos que no se han tocado. Esto tiene mala escalación y puede generar bugs sutiles, cuando dos clientes modifican el mismo recurso en paralelo.
Existe un caso donde PUT sí tiene sentido para actualizaciones, que es cuando el recurso es un documento que se reemplaza por completo de forma explícita, por ejemplo un fichero de configuración, una imagen, etc. Semánticamente es correcto usar PUT porque efectivamente sustituyes el recurso entero.
El error que más aparece en APIs heredadas
Hay un patrón que se repite en códigos heredados, APIs que solo tienen POST, ya sea para crear, para actualizar e incluso para borrar. Funciona, pero pierdes la expresividad del protocolo HTTP. Sin leer la documentación no puedes inferir que hace la API, y un nuevo programador debe leer el código para entender que hace el endpoint.
REST no obliga a nada, pero cuando sigues la semántica HTTP obtienes décadas de convenciones, tooling y comprensión compartida que ya viene resuelta. No aprovecharla es deuda técnica silenciosa que alguien pagará más adelante.
La próxima vez que abras ese controlador con el ticket de "actualizar el email del usuario": PATCH, sin dudar.
* Imagen generada por IA *