Métodos HTTP

GET, POST, PUT, PATCH y DELETE, y cuáles se pueden repetir sin riesgo.

El método es la primera palabra de toda petición. Le dice al servidor qué quieres hacer con el recurso de la URL. La misma URL, /api/Books/1inicia sesión para verlo, devuelve un libro con GET, lo cambia con PUT o PATCH y lo elimina con DELETE. La mayoría de las APIs usan cinco métodos para casi todo, y de vez en cuando aparecen algunos más.

Con los métodos empieza a importar la diferencia entre un navegador y una herramienta de pruebas. La barra de direcciones solo envía GET. Para lo demás necesitas curl, Postman o un botón Enviar como los de esta página. Los ejemplos que cambian datos compartidos usan curl, para que los botones Enviar de esta página no modifiquen registros que otras personas están viendo. La lección 6, Enviar peticiones con curl y Postman, prepara bien las dos herramientas. Si todavía no tienes curl, lee aquí los ejemplos de curl y ejecútalos después de esa lección.

Los cinco métodos de cada día

MétodoQué haceEstado de éxito típico
GETLee un recurso o una lista. No envía cuerpo.200
POSTCrea un recurso nuevo o lanza una acción.201, a veces 200 o 202
PUTSustituye un recurso por el cuerpo que envías.200 o 204
PATCHCambia algunos campos de un recurso.200 o 204
DELETEElimina un recurso.200 o 204

Cada uno de los endpoints de métodos de esta API acepta un verbo y describe lo que recibió. Envía un GET:

GET/api/methods/get?x=1

Y un POST con cuerpo:

POST/api/methods/post
{"hello":"world"}

Estos endpoints no guardan nada. Para ver qué hacen los métodos con datos reales necesitas un recurso, y el resto de esta lección usa libros.

POST crea

Crea un libro:

Terminal
curl -i -X POST https://api.testingapis.com/api/Books \
  -H "Content-Type: application/json" \
  -d '{"title":"The Pragmatic Programmer","author":"David Thomas",
       "isbn":"9780135957059","genre":"programming","publishedYear":2019,
       "pages":352,"language":"English","price":42.5,"rating":4.6,
       "available":true,"tags":["career"]}'
Este ejemplo usa endpoints para miembros con sesión iniciada.Iniciar sesiónCrear una cuenta gratuita
Respuesta
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8

{
  "message": "Book created successfully",
  "data": {
    "id": 29,
    "title": "The Pragmatic Programmer",
    "author": "David Thomas",
    "isbn": "9780135957059",
    "genre": "programming",
    "publishedYear": 2019,
    "pages": 352,
    "language": "English",
    "price": 42.5,
    "rating": 4.6,
    "available": true,
    "tags": ["career"]
  }
}

El servidor respondió 201 Created, asignó el ID 29 y devolvió el registro nuevo. A ti te saldrá otro ID, porque otras personas también crean libros. Es lo primero que hay que probar de cualquier POST: el ID lo elige el servidor, y tu test tiene que leerlo de la respuesta en lugar de adivinarlo.

Muchas APIs también envían una cabecera Location con el 201, con la URL del recurso nuevo, por ejemplo Location: /api/Books/29inicia sesión para verlo. Esta no. El estándar no lo exige, pero si la documentación lo prometiera, sería un bug.

Ahora ejecuta el mismo POST una segunda vez. Obtienes un segundo libro con un ID nuevo, idéntico salvo por ese número. POST no es seguro de repetir: cada llamada crea algo nuevo. Tenlo en cuenta para la tabla de más abajo.

PUT sustituye, PATCH cambia

Los dos métodos actualizan un registro, y la gente los confunde constantemente. La diferencia aparece en los campos que omites.

PATCH

Cambia solo el precio del libro 29:

Terminal
curl -X PATCH https://api.testingapis.com/api/Books/29 \
  -H "Content-Type: application/json" \
  -d '{"price":39.99}'
Este ejemplo usa endpoints para miembros con sesión iniciada.Iniciar sesiónCrear una cuenta gratuita
Respuesta
{
  "message": "Book patched successfully",
  "changed": ["price"],
  "ignored": [],
  "data": {
    "id": 29,
    "title": "The Pragmatic Programmer",
    "author": "David Thomas",
    "isbn": "9780135957059",
    "genre": "programming",
    "publishedYear": 2019,
    "pages": 352,
    "language": "English",
    "price": 39.99,
    "rating": 4.6,
    "available": true,
    "tags": ["career"]
  }
}

Solo cambió el precio. Todo lo que no mencionaste conservó su valor. Esta API además te dice qué campos cambió, algo poco habitual y muy útil para probar.

PUT

Ahora envía un PUT con solo tres campos:

Terminal
curl -X PUT https://api.testingapis.com/api/Books/29 \
  -H "Content-Type: application/json" \
  -d '{"title":"The Pragmatic Programmer","author":"David Thomas","price":39.99}'
Este ejemplo usa endpoints para miembros con sesión iniciada.Iniciar sesiónCrear una cuenta gratuita
Respuesta
{
  "message": "Book updated successfully",
  "data": {
    "id": 29,
    "title": "The Pragmatic Programmer",
    "author": "David Thomas",
    "isbn": "",
    "genre": "",
    "publishedYear": 0,
    "pages": 0,
    "language": "",
    "price": 39.99,
    "rating": 0,
    "available": false,
    "tags": []
  }
}

El ISBN, el género, el año, el número de páginas, el idioma, la valoración y las etiquetas han desaparecido. PUT significa "aquí tienes la nueva versión completa de este recurso". Cualquier campo que no enviaste se sustituyó por un valor vacío o cero. La API hizo exactamente lo que define PUT, y aun así borró casi todo un registro porque el cliente envió un cuerpo parcial.

Es uno de los bugs reales más comunes en las apps. Un desarrollador usa PUT para actualizar un campo, envía solo ese campo y todos los demás campos del registro se borran sin hacer ruido. Cuando pruebes una función de actualización, envía un cuerpo parcial y luego haz GET del registro para ver qué sobrevivió.

Algunas APIs lo gestionan de otra manera. Tratan un PUT con campos ausentes como un PATCH, o lo rechazan con un 400 porque faltan campos obligatorios. Esta última opción es probablemente la más segura. Qué pruebas depende de la documentación, pero siempre lo pruebas.

DELETE elimina

Borra el libro 29 y luego intenta borrarlo otra vez:

Terminal
$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"message":"Book deleted successfully"}          (200 OK)

$ curl -X DELETE https://api.testingapis.com/api/Books/29
{"status":404,"error":"Not Found","message":"Book with ID 29 does not exist."}   (404 Not Found)
Este ejemplo usa endpoints para miembros con sesión iniciada.Iniciar sesiónCrear una cuenta gratuita

La primera llamada devuelve 200 con un mensaje. La segunda devuelve 404, porque ya no queda nada que borrar. Después de un borrado, comprueba también que un GET del mismo ID devuelve 404. Algunos sistemas solo marcan los registros como borrados y los siguen devolviendo, lo que es un bug si la documentación dice lo contrario.

Métodos seguros e idempotentes

Dos propiedades del estándar HTTP deciden cómo pueden tratar los clientes y los tests a cada método.

Un método seguro no cambia nada en el servidor. Puedes llamarlo tantas veces como quieras. Un método idempotente puede cambiar cosas, pero llamarlo dos veces tiene el mismo efecto en el servidor que llamarlo una.

MétodoSeguroIdempotente
GETSíSí
HEADSíSí
OPTIONSSíSí
PUTNoSí
DELETENoSí
POSTNoNo
PATCHNoNo garantizado

PUT es idempotente porque enviar dos veces el mismo registro completo deja el mismo registro. DELETE es idempotente aunque la segunda llamada recibiera un 404: la respuesta fue distinta, pero el estado del servidor (el libro 29 no existe) es el mismo en los dos casos. La idempotencia trata del efecto, no de la respuesta. POST no es idempotente, como viste cuando el segundo POST creó un segundo libro. PATCH puede ser una cosa u otra. Poner el precio a 39.99 es idempotente; un patch que dice "suma 1 al stock" no lo es.

Esto importa porque las redes fallan. Si una petición se queda sin respuesta, el cliente no sabe si el servidor la recibió. Reintentar un GET o un PUT es inofensivo. Reintentar un POST puede cobrar dos veces una tarjeta. Los navegadores, los proxies y las librerías HTTP usan estas reglas para decidir si reintentan por su cuenta, y Timeouts, reintentos y límites de frecuencia muestra cómo las APIs hacen que POST sea seguro de reintentar con claves de idempotencia.

Para quien prueba, estas reglas dan casos de prueba gratis. Llama a un GET y comprueba que nada cambió. Llama dos veces a un PUT y comprueba que la segunda llamada no creó ni duplicó nada. Un GET que cambia datos (como /api/deleteUser?id=5inicia sesión para verlo, que existe en algunos proyectos) es un error de diseño, porque los rastreadores, las vistas previas de enlaces y la precarga del navegador envían peticiones GET sin preguntar.

HEAD y OPTIONS

HEAD es un GET sin cuerpo. El servidor envía el mismo estado y las mismas cabeceras que para un GET y se detiene ahí. Los clientes lo usan para comprobar si algo existe o cuánto ocupa un archivo sin descargarlo.

curl -I https://api.testingapis.com/api/methods/head
HTTP/1.1 200 OK
x-message: HEAD request received. Responses to HEAD carry headers only.
x-method: HEAD

OPTIONS pregunta qué está permitido en una URL. Los navegadores lo envían automáticamente antes de algunas peticiones de otro origen (un "preflight"), y por eso aparece en los registros de red aunque ningún código lo llame. Rara vez lo probarás directamente, salvo que trabajes con la configuración de CORS.

OPTIONS/api/methods/options

Usar el método equivocado

/api/methods/get solo acepta GET. Envíale un POST con curl -i -X POST https://api.testingapis.com/api/methods/get:

Respuesta
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: GET

405 Method Not Allowed significa que la URL existe pero no admite ese método. La cabecera allow enumera los que sí admite. Un POST a un libro concreto, que no tiene sentido porque los libros se crean en /api/Booksinicia sesión para verlo, recibe el mismo trato:

curl -i -X POST https://api.testingapis.com/api/Books/1
HTTP/1.1 405 Method Not Allowed
Content-Length: 0
allow: DELETE, GET, HEAD, PATCH, PUT
Este ejemplo usa endpoints para miembros con sesión iniciada.Iniciar sesiónCrear una cuenta gratuita

Enviar todos los métodos a todos los endpoints es un test barato que encuentra problemas reales. Buscas métodos que deberían rechazarse y no se rechazan (un DELETE que funciona en un endpoint que debía ser de solo lectura), y 500 o 404 donde corresponde un 405. Fíjate en que esta API devuelve el 405 con el cuerpo vacío, mientras que sus otros errores son JSON. Los clientes que esperan un cuerpo de error JSON en todo 4xx tropezarán con él.

Ejercicio

Hazlo con curl (o vuelve aquí después de la lección de curl).

  • Crea un libro con POST. Apunta su ID.
  • Haz PATCH de su título. Haz GET y confirma que solo cambió el título.
  • Haz PUT con un cuerpo que tenga todos los campos menos tags. ¿Qué les pasó a las etiquetas?
  • Envía el mismo PUT otra vez. ¿Cambió algo la segunda vez?
  • Haz DELETE, GET y DELETE otra vez. Apunta los tres códigos de estado.
  • Envía un DELETE a /api/Booksinicia sesión para verlo (sin ID). Antes de ejecutarlo, predice el código de estado. Después mira la cabecera allow.

Practica y ve más allá