HTTP en Serio — Qué Viaja Cuando Llamas a una API
Volver a clases
Backend Development●●Intermedio

HTTP en Serio — Qué Viaja Cuando Llamas a una API

120 min

Ya escribes fetch. Ahora entiende qué se manda realmente, por qué falla CORS, y la diferencia entre un 401 y un 403.

Etiquetas

#http#api#cors

Ya usas HTTP, aunque no lo hayas visto

Llevas semanas escribiendo fetch. Cada vez que lo haces, tu navegador arma un mensaje de texto con un formato muy específico, lo manda por la red, y recibe otro de vuelta. fetch es solo una fachada cómoda sobre ese intercambio.

Hoy abrimos la caja. No es un tema "de backend": es el idioma en el que tu frontend habla con el mundo, y es donde vas a pasar buena parte de tus horas de depuración durante toda tu carrera.

La conversación: petición y respuesta

HTTP funciona por turnos estrictos: el cliente pide, el servidor responde, y la conversación termina. El servidor no puede hablarte por iniciativa propia — esa limitación es justo la que motiva los WebSockets, que verás en el mapa del ecosistema.

Cada mensaje tiene tres partes: la línea inicial (método y ruta, o código de estado), los headers (metadatos) y el cuerpo (los datos, opcional). El primer snippet muestra ambos lados completos: vale la pena que lo leas de arriba abajo.

Y algo que sorprende: HTTP no tiene memoria. Es stateless: cada petición llega sin saber nada de las anteriores. El servidor no "recuerda" que iniciaste sesión — se lo tienes que recordar en cada llamada, normalmente con una cookie o un header Authorization. Todo el tema de autenticación existe para resolver esa amnesia.

Los métodos dicen la intención

GET para leer, POST para crear, PUT/PATCH para modificar, DELETE para borrar. Pero hay dos propiedades que importan más que la lista:

  • Seguro: no modifica nada (solo GET). Por eso el navegador puede pedir un GET de nuevo sin preguntarte, y precargarlo.
  • Idempotente: repetirlo N veces deja el sistema igual que hacerlo una vez.

POST no es idempotente, y de ahí sale el bug clásico del doble clic en "Pagar": dos cargos. Por eso los formularios deshabilitan el botón mientras envían — el mismo estado enviando que te faltaba en el reto de validación.

Los códigos de estado no son decoración

El código es lo primero que lee cualquier cliente, caché o monitor. Elegirlo mal hace que tu API mienta. Las tres confusiones que verás cada semana:

  • 401 vs 403"no sé quién eres" contra "sé quién eres y no puedes". El primero se arregla iniciando sesión; el segundo no. Si los confundes, tu app manda al login a alguien que ya inició sesión, en bucle.
  • 4xx vs 5xx — de quién es la culpa. Un dato inválido es 4xx (culpa del cliente); una excepción no controlada es 5xx (culpa del servidor). Los clientes bien hechos reintentan los 5xx: si respondes 500 a un formulario mal llenado, provocas reintentos infinitos.
  • 200 con error adentro — el pecado capital. Rompe caché, monitoreo y reintentos, y obliga a escribir código defensivo en cada llamada.

Recuerda de la Auditoría IA #2: fetch no rechaza ante un 404 o un 500 — solo ante fallos de red. Por eso siempre revisas response.ok antes del .json().

Headers: los que sí vas a usar

De los cientos que existen, este puñado cubre tu semestre:

HeaderPara qué
Content-TypeQué formato mandas (application/json)
AcceptQué formato quieres recibir
AuthorizationQuién eres (Bearer <token>)
Cache-ControlCuánto se puede guardar la respuesta
LocationDónde quedó lo que acabas de crear (con 201)

CORS: el error que todos sufren

El día que tu fetch truene con "blocked by CORS policy", acuérdate de esto: el bloqueo lo hace tu navegador, no el servidor. La petición salió y la respuesta llegó; el navegador simplemente no te deja leerla porque el servidor no autorizó tu origen.

De ahí las tres consecuencias del cuarto snippet, y sobre todo esta: CORS se arregla en el servidor. No hay opción de fetch, ni truco, ni librería del lado del cliente que lo resuelva. Si la API es ajena y no te autoriza, necesitas un proxy propio.

Caché: lo que hace rápida a la web

Una respuesta que no se pide es infinitamente más rápida que una optimizada. Con Cache-Control el servidor dice cuánto tiempo puede guardarse algo; con 304 Not Modified responde "no cambió, usa tu copia" sin reenviar el contenido.

Esto conecta con tu deploy: los sitios estáticos (como esta plataforma) son rápidos precisamente porque casi todo se cachea. Y es la razón de que GET sea cacheable y POST no — otra ventaja que se pierde cuando una API manda todo por POST.

HTTPS, en una línea

HTTPS es HTTP dentro de un túnel cifrado. Sin él, cualquiera en la misma red Wi-Fi puede leer los datos que viajan, incluidos tokens y contraseñas. Hoy es obligatorio: los navegadores marcan como "no seguro" cualquier sitio sin él, y varias APIs del navegador (cámara, ubicación) simplemente no funcionan en HTTP. Tu deploy ya lo trae; no lo desactives.

Lo que te llevas

Cuando algo falle —y va a fallar— tu primer reflejo ya no debería ser cambiar líneas al azar. Abre Network, mira el método, el código de estado y los headers, y en 30 segundos sabrás si el problema es tuyo, del servidor o del navegador. Esa lectura es la diferencia entre depurar y adivinar.

Ejemplos de Código

4 ejemplos

Anatomía de una petición y su respuesta

Esto es lo que tu fetch construye por ti.

http
1POST /api/productos HTTP/1.1          ← método, ruta, versión
2Host: api.mitienda.com
3Content-Type: application/json        ← qué formato estoy MANDANDO
4Accept: application/json              ← qué formato quiero RECIBIR
5Authorization: Bearer eyJhbGci...     ← quién soy
6
7{"nombre":"Teclado","precio":450}     ← el cuerpo
8
9─────────────────────────────────────
10
11HTTP/1.1 201 Created                  ← código de estado
12Content-Type: application/json
13Location: /api/productos/42           ← dónde quedó lo que creaste
14Cache-Control: no-store
15
16{"id":42,"nombre":"Teclado","precio":450}

Los códigos que vas a usar de verdad

No memorices los 60; domina estos.

text
12xx — salió bien
2  200 OK                 respuesta normal con cuerpo
3  201 Created            creaste algo (devuelve Location)
4  204 No Content         salió bien y no hay nada que devolver (DELETE)
5
63xx — redirección
7  301 Moved Permanently  cambió de dirección para siempre
8  304 Not Modified       úsalo de tu caché, no cambió
9
104xx — el cliente se equivocó
11  400 Bad Request        petición malformada
12  401 Unauthorized       NO SÉ QUIÉN ERES        → manda al login
13  403 Forbidden          sé quién eres, NO PUEDES → el login no ayuda
14  404 Not Found          no existe
15  409 Conflict           choca con el estado actual (correo duplicado)
16  422 Unprocessable      sintaxis ok, datos inválidos
17  429 Too Many Requests  bájale al ritmo
18
195xx — el servidor se rompió
20  500 Internal Error     bug del lado del servidor
21  502 Bad Gateway        el proxy no obtuvo respuesta
22  503 Unavailable        caído o saturado (temporal)

Métodos, seguridad e idempotencia

Idempotente = repetirlo 10 veces deja el mismo resultado que hacerlo 1 vez.

text
1Método   ¿Modifica?  ¿Idempotente?  Uso típico
2───────────────────────────────────────────────────────────
3GET      no          sí             leer  (cacheable)
4POST     sí          NO             crear (repetirlo DUPLICA)
5PUT      sí          sí             reemplazar completo
6PATCH    sí          depende        modificar parcial
7DELETE   sí          sí             eliminar
8
9Por qué importa: si una petición falla por red, un cliente puede
10reintentarla sin miedo SOLO si es idempotente. Por eso el doble clic
11en "Pagar" (POST) genera dos cargos y el doble clic en "Guardar" (PUT)
12no genera dos registros.

CORS — por qué falla en el navegador y no en Postman

El bloqueo lo hace TU navegador, no el servidor.

text
1Tu app en   http://localhost:3000
2llama a     https://api.otrodominio.com/datos
3
4→ El navegador aplica la política del mismo origen y BLOQUEA la
5  respuesta, salvo que el servidor autorice tu origen:
6
7  Access-Control-Allow-Origin: http://localhost:3000
8
9Consecuencias prácticas:
10• Postman y curl NO tienen esta restricción → "en Postman sí funciona"
11  no significa que tu código esté mal.
12• CORS se arregla en el SERVIDOR, no en tu fetch. Ninguna opción de
13  fetch lo desactiva.
14• Antes de peticiones "no simples" el navegador manda un OPTIONS
15  (preflight): verás DOS peticiones en Network. Es normal.

Recursos

4 recursos disponibles

¡Hora de Practicar!

PrácticaIntermedio40 min🟡 IA con bitácora

Práctica Guiada — Autopsia de una petición

Abre la pestaña Network y mira lo que llevas semanas mandando a ciegas.

Con las DevTools abiertas en la pestaña Network, sobre tu proyecto o cualquier sitio:

  1. Recarga la página y observa la lista de peticiones. Identifica el documento HTML, el CSS, el JS y las imágenes.
  2. Haz clic en una petición y localiza: método, código de estado, Request Headers y Response Headers.
  3. Encuentra el Content-Type de la respuesta. ¿Coincide con lo que esperabas?
  4. Filtra por Fetch/XHR y dispara una petición de tu app. Copia su curl (clic derecho → Copy as cURL) y córrelo en tu terminal.
  5. Provoca un error a propósito: cambia la URL de tu fetch a una ruta inexistente. Compara qué ves en Network contra qué ve el usuario en pantalla.
  6. En Size, busca la petición más pesada de tu sitio. ¿Es una imagen? ¿Cuánto pesa?

Entrega: captura de la petición más pesada y una tabla con las 5 peticiones de tu app (método, ruta, estado, tamaño).

Desafío de Código

EjercicioIntermedio25 min🔴 Sin IA

Ejercicios — ¿Qué código de estado responderías?

Elegir el código correcto es diseñar una API que no miente.

Para cada escenario, di qué código de estado debería devolver el servidor y por qué:

  1. El usuario pide /api/productos/9999 y ese producto no existe.
  2. El usuario pide /api/mi-perfil sin haber iniciado sesión.
  3. El usuario inició sesión, pero pide /api/admin/usuarios y no es administrador.
  4. Se crea un producto nuevo correctamente.
  5. El usuario manda el formulario de registro con el correo vacío.
  6. La base de datos está caída.
  7. El usuario intenta registrarse con un correo que ya existe.
  8. Se elimina un producto y no hay nada que devolver.

Después responde: ¿por qué es un problema que una API responda 200 con un mensaje de error en el cuerpo?

Reto de Lectura

Reto de LecturaIntermedio35 min🔴 Sin IA

Reto de Lectura — La API que miente

Una API real puede ser sintácticamente correcta y semánticamente un desastre. Diagnostica seis decisiones que le harán la vida imposible a quien la consuma.

Te contrataron para consumir esta API desde el frontend. Antes de escribir una línea, audita el contrato: encuentra los 6 problemas y explica qué le rompen a quien la usa.

POST /api/obtenerProductos
→ 200 OK
  { "error": false, "data": [ ... ] }

POST /api/obtenerProducto?id=9999
→ 200 OK
  { "error": true, "mensaje": "Producto no encontrado", "data": null }

POST /api/borrarProducto
→ 200 OK
  { "error": false }

POST /api/miPerfil          (sin haber iniciado sesión)
→ 404 Not Found
  { "error": true, "mensaje": "No autorizado" }

POST /api/crearProducto     (nombre vacío)
→ 500 Internal Server Error
  { "error": true, "mensaje": "Error" }

Response Headers de todas:
  Content-Type: text/html

Preguntas:

  1. Todo es POST. ¿Qué se pierde? Piensa en caché, en el botón atrás y en compartir un link.
  2. El "no encontrado" responde 200. ¿Cómo obliga eso a escribir el código del frontend?
  3. El perfil sin sesión responde 404 diciendo "No autorizado". ¿Qué código correspondía y por qué importa la diferencia?
  4. La validación fallida responde 500. ¿De quién es la culpa según ese código, y de quién era realmente?
  5. Content-Type: text/html devolviendo JSON. ¿Qué se rompe?
  6. Las rutas se llaman /obtenerProductos, /borrarProducto. ¿Cómo se llamarían en REST?

Documentación Oficial

DocumentaciónPrincipiante15 min

Documentación de apoyo

Referencias para consultar durante el curso.