Skip to content

🎯 Meta: conocer las alternativas y complementos a la API REST que aprendiste. REST es el 80% de los casos, pero hay problemas donde otras tecnologías encajan mejor. Saber cuándo usar cada una es criterio de ingeniero senior.


D.1 · El panorama: cuándo usar qué

┌──────────────┬─────────────────────────────┬──────────────────────────┐
│ Tecnología   │ Ideal para                   │ Ejemplo                  │
├──────────────┼─────────────────────────────┼──────────────────────────┤
│ REST         │ CRUD, APIs públicas, lo común│ La API de tu blog        │
│ WebSockets   │ Tiempo real bidireccional    │ Chat, notificaciones     │
│ GraphQL      │ Clientes que piden datos     │ App móvil con pantallas   │
│              │ flexibles, evitar over-fetch │ que necesitan datos varios│
│ gRPC         │ Comunicación entre servicios │ Microservicios internos  │
│ SSE          │ Server → cliente, un sentido │ Feed de eventos, progreso│
└──────────────┴─────────────────────────────┴──────────────────────────┘

🧠 No es "REST vs los demás". Casi siempre REST es la base y añades otra tecnología para un problema concreto (WebSockets para el chat, gRPC entre dos servicios internos). Se combinan.


D.2 · WebSockets — tiempo real bidireccional

El problema de REST: el cliente pregunta, el servidor responde. El servidor no puede iniciar una comunicación ("acaba de llegarte un mensaje"). Para saber si hay algo nuevo, el cliente tendría que preguntar cada segundo (polling: ineficiente).

WebSockets: abren una conexión permanente y bidireccional. Cliente y servidor se envían mensajes en cualquier momento.

REST (polling):                      WebSocket:
Cliente: ¿hay algo nuevo? No         Conexión abierta ⟷ permanente
Cliente: ¿hay algo nuevo? No         Servidor → "¡Nuevo mensaje!" (cuando pasa)
Cliente: ¿hay algo nuevo? Sí         Cliente → "escribiendo..." (cuando quiere)
(desperdicio)                        (eficiente, instantáneo)

Casos de uso: chats, notificaciones en vivo, dashboards en tiempo real, juegos, edición colaborativa (tipo Google Docs), precios/tickers.

Ejemplo en Elysia (Bun brilla en tiempo real):

typescript
new Elysia()
  .ws('/chat', {
    open(ws) {
      ws.subscribe('sala-general')          // se une a una sala
    },
    message(ws, mensaje) {
      // reenvía el mensaje a todos los de la sala
      ws.publish('sala-general', { usuario: ws.data.user, texto: mensaje })
    },
    close(ws) {
      ws.unsubscribe('sala-general')
    },
  })
  .listen(3000)

Por stack:

  • NestJS: @nestjs/websockets con gateways (@WebSocketGateway).
  • Laravel: Laravel Reverb (servidor WebSocket propio) + Echo.
  • FastAPI: soporte nativo @app.websocket("/ws").
  • Go: gorilla/websocket o nhooyr/websocket.

⚠️ El reto de escalar WebSockets: las conexiones son stateful (el servidor recuerda quién está conectado). Con varios servidores, un usuario en el servidor A no ve los mensajes que llegan al servidor B. Solución: Redis Pub/Sub (apéndice B) como "bus" que comparte los mensajes entre todos los servidores. Por eso Redis y WebSockets van de la mano.

💡 Alternativa más simple — SSE (Server-Sent Events): si solo necesitas que el servidor empuje datos al cliente (progreso de una tarea, feed de noticias) y NO al revés, SSE es más simple que WebSockets (es HTTP normal, una conexión de solo lectura). Para un chat necesitas WebSockets; para "notificar al cliente", SSE basta y sobra.


D.3 · GraphQL — el cliente pide exactamente lo que necesita

El problema de REST: over-fetching (te trae más datos de los que quieres) y under-fetching (necesitas varias llamadas para armar una pantalla).

REST: para pintar un perfil con sus últimos posts y sus comentarios necesitas:
  GET /usuarios/1
  GET /usuarios/1/posts
  GET /posts/{id}/comentarios (x cada post)
  → muchas llamadas (under-fetching)

GraphQL: UNA sola llamada, pidiendo exactamente los campos que quieres:
graphql
query {
  usuario(id: 1) {
    nombre
    posts(ultimos: 5) {
      titulo
      comentarios { texto }
    }
  }
}

El servidor devuelve justo esa forma, ni más ni menos. El cliente controla qué datos recibe.

Casos de uso: apps móviles (ahorran datos y llamadas), frontends complejos con muchas vistas distintas, APIs consumidas por muchos clientes con necesidades diferentes.

Herramientas 2026: Apollo Server, GraphQL Yoga (Node/Nest), Strawberry/Ariadne (Python), gqlgen (Go). NestJS tiene integración de primera con @nestjs/graphql.

⚠️ GraphQL no es gratis. Añade complejidad: el problema N+1 se vuelve fácil de provocar (cada campo anidado puede ser una consulta) — se resuelve con DataLoader (batching). Y hay que cuidar la seguridad: limitar la profundidad y complejidad de las consultas (un atacante puede pedir una query anidada gigante para tumbar el servidor). No adoptes GraphQL "porque mola": úsalo cuando la flexibilidad de datos sea un problema real. Para un CRUD, REST es más simple.

🧠 REST vs GraphQL en una frase: REST expone recursos (el servidor decide la forma); GraphQL expone un grafo de datos (el cliente decide la forma). Ninguno es "mejor": dependen de quién debe tener el control de la forma de los datos.


D.4 · gRPC — comunicación rápida entre servicios

Cuando tienes microservicios (cap. 11) que hablan entre sí, REST+JSON es cómodo pero lento (texto, HTTP/1.1). gRPC es un protocolo binario, rapidísimo, para comunicación servicio a servicio.

  • Usa Protocol Buffers (protobuf): defines el contrato en un archivo .proto y genera el código cliente/servidor en cualquier lenguaje automáticamente.
  • Corre sobre HTTP/2 (multiplexado, streaming), binario (más compacto y rápido que JSON).
protobuf
// usuarios.proto — el contrato, compartido entre servicios
service UsuarioService {
  rpc ObtenerUsuario (UsuarioRequest) returns (Usuario);
}
message UsuarioRequest { int64 id = 1; }
message Usuario { int64 id = 1; string nombre = 2; string email = 3; }

De ese .proto generas el código en Go, Python, Node, etc. Los servicios se llaman como si fueran funciones locales, con tipos garantizados en ambos extremos.

Casos de uso: comunicación interna entre microservicios, sistemas de alto rendimiento y baja latencia. NO para navegadores (soporte limitado; ahí usas REST o GraphQL de cara al público).

🧠 La regla práctica: REST/GraphQL de cara al mundo (navegadores, apps, terceros); gRPC puertas adentro (entre tus propios servicios). Go (cap. 08) es un lenguaje estrella para gRPC.

🔗 Recuerda el consejo del cap. 11: empieza con un monolito. gRPC resuelve un problema (comunicación entre servicios) que solo tienes cuando ya tienes microservicios. No lo necesitas hasta entonces.


D.5 · Webhooks — notificar a otros sistemas

Un webhook es "REST al revés": en vez de que tú preguntes a un servicio, el servicio te llama a ti cuando pasa algo. Es cómo Stripe te avisa de un pago, GitHub de un push, etc.

Tú registras una URL:  https://miapp.com/webhooks/stripe
Cuando hay un pago, Stripe hace: POST https://miapp.com/webhooks/stripe { evento... }
Tú procesas el evento (idealmente encolándolo, apéndice B).

⚠️ Seguridad de webhooks (crítico): cualquiera podría hacer POST a tu URL fingiendo ser Stripe. Verifica siempre la firma que el proveedor incluye (un HMAC con un secreto compartido). Sin verificar la firma, un atacante podría marcarte pagos falsos como completados.

python
@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
    firma = request.headers.get("Stripe-Signature")
    payload = await request.body()
    evento = verificar_firma(payload, firma, WEBHOOK_SECRET)   # ← imprescindible
    # encola el procesamiento y responde 200 rápido:
    await cola.add("procesar_pago", evento)
    return {"recibido": True}

💡 Responde rápido (200) y procesa en segundo plano (cola, apéndice B). Los proveedores reintentan si tardas o fallas; si procesas todo en la petición y tarda, puedes recibir eventos duplicados. Haz tu handler idempotente (procesar el mismo evento dos veces no debe duplicar el efecto).


D.6 · Documentación de APIs (OpenAPI)

Sea cual sea tu API REST, documéntala con OpenAPI (antes Swagger). Ya lo tienes casi gratis:

StackCómo
FastAPIAutomático en /docs (cap. 05)
Elysia@elysiajs/swagger (cap. 07)
NestJS@nestjs/swagger con decoradores
LaravelScramble o L5-Swagger
Goswaggo/swag desde comentarios

💡 Una API sin documentación es una API que nadie sabe usar. OpenAPI te da: docs interactivas, generación de clientes automática y contratos verificables. Actívalo desde el día 1. FastAPI y Elysia lo generan desde tus tipos/esquemas sin esfuerzo — otra razón para validar bien.


D.7 · Versionado y evolución de APIs

Tu API tendrá clientes (frontend, móviles, terceros) que no puedes romper de un día para otro:

✅ Versiona desde el día 1: /api/v1/... (cap. 00)
✅ Cambios compatibles: añadir campos/endpoints está bien; quitar o renombrar, NO.
✅ Para cambios que rompen: crea /api/v2 y mantén v1 un tiempo (deprecación gradual).
✅ Comunica las deprecaciones (cabecera Deprecation, docs, avisos).

🧠 Regla del "contrato": una vez publicada, tu API es una promesa a sus clientes. Añadir es seguro; quitar o cambiar el significado de algo rompe a alguien. Piensa cada respuesta como un contrato que tendrás que mantener.


D.8 · Tabla de decisión rápida

Necesito...Usa
CRUD estándar, API públicaREST
Chat, notificaciones en vivo, colaboraciónWebSockets (+ Redis para escalar)
Solo empujar datos server→cliente (progreso, feed)SSE
Clientes que piden datos a medida (móvil, front complejo)GraphQL
Comunicación rápida entre mis microserviciosgRPC
Que otro sistema me avise de eventosWebhooks (verifica firma)
Tarea lenta sin bloquear la peticiónCola (apéndice B)

D.9 · Idempotencia en REST: el patrón Idempotency-Key

El webhook idempotente de D.5 resuelve "no proceses el mismo evento dos veces". El mismo problema existe al revés: tu cliente llama a POST /pagos pero la red falla justo al recibir la respuesta — ¿reintenta y arriesga cobrar dos veces, o no reintenta y arriesga que nunca se cobrara en realidad? Stripe popularizó la solución: una cabecera Idempotency-Key.

Cliente genera una clave única (UUID v4) POR OPERACIÓN, antes de la primera petición:

POST /pagos
Idempotency-Key: 7b3e1a2c-...

Si la red falla y el cliente reintenta con la MISMA clave:
  → el servidor devuelve la respuesta GUARDADA de la primera vez (200, o el error que fuera),
    SIN volver a cobrar. No es "otra petición", es "la misma petición, otra vez".
python
@app.post("/pagos")
async def crear_pago(datos: PagoInput, idempotency_key: str = Header(...)):
    existente = await cache.get(f"idem:{idempotency_key}")
    if existente:
        return JSONResponse(existente["cuerpo"], status_code=existente["status"])   # respuesta cacheada

    resultado = await procesar_pago(datos)                 # se ejecuta UNA sola vez
    await cache.set(f"idem:{idempotency_key}", {"cuerpo": resultado, "status": 200}, ex=86400)
    return resultado

🧠 La clave la genera el CLIENTE, no el servidor — al contrario que un ID de recurso. Debe ser única por intento lógico de operación (un UUID v4 nuevo por cada pago distinto, el MISMO UUID en cada reintento de ese pago). Guarda la clave y la respuesta 24h (Redis con TTL, ap. B) y es suficiente para la ventana real de reintentos de cualquier cliente HTTP.

⚠️ GET/DELETE ya son idempotentes por definición HTTP (repetirlos no cambia el resultado); el problema es real solo en POST (y a veces PATCH) — cualquier operación que crea o cobra algo. No necesitas esto en un GET /productos.


✅ Ejercicio del apéndice

Amplía una de tus APIs del blog con tiempo real:

1. Añade un endpoint WebSocket que notifique a los lectores cuando se publica
   un artículo nuevo (usa Elysia o NestJS gateway).
2. Escala mentalmente: si tuvieras 2 servidores, ¿cómo compartirían los mensajes?
   (pista: Redis Pub/Sub, apéndice B). Impleméntalo si te animas.
3. Añade un webhook entrante simulado (POST /webhooks/pago) que VERIFIQUE una
   firma HMAC y encole el procesamiento.
4. Documenta tu API con OpenAPI y genera la interfaz interactiva.
5. Reflexiona: ¿alguna parte de tu app se beneficiaría de GraphQL? ¿Por qué sí o no?
💡 Pistas
  • Para el punto 3, genera la firma con hmac.new(secreto, cuerpo, sha256).hexdigest() en un script de prueba que simule al proveedor, y compárala en tu endpoint con hmac.compare_digest (nunca == a secas — evita timing attacks).
  • El punto 2 no necesita más que un redis.publish() en el servidor que emite el evento y un redis.subscribe() en cada instancia que reenvía a sus propios clientes WebSocket conectados — el mismo patrón del apéndice B.6.
  • Para el punto 5: si tu frontend ya pide "todo el artículo" con un solo GET, GraphQL no te aporta mucho. Si tuvieras varias pantallas pidiendo subconjuntos distintos de los mismos datos (móvil vs web, por ejemplo), ahí empieza a justificarse.

🧠 Autoevaluación

SSE es HTTP normal: atraviesa proxies sin configuración especial, reconecta solo, y usa tu autenticación de cookies existente. WebSockets añade un protocolo distinto y más piezas que mantener (heartbeat, reconexión manual) — solo se justifica cuando el cliente también emite con frecuencia por el mismo canal.

Que el handler sea idempotente: comprobar un identificador único del evento antes de procesarlo (una tabla de eventos ya procesados) y responder OK sin repetir el efecto si ya se vio antes. Es el mismo patrón de idempotencia de las colas del apéndice B.

Una API publicada es un contrato: los clientes existentes (apps móviles ya instaladas, integraciones de terceros) dependen de la forma actual de la respuesta. Añadir un campo no rompe a nadie que lo ignore; quitar uno rompe a cualquier cliente que lo estuviera leyendo, sin forma de que lo sepan de antemano.

En REST, el servidor decide la forma de cada endpoint — el cliente recibe lo que ese endpoint define, ni más ni menos. En GraphQL, el cliente especifica exactamente qué campos necesita en cada petición, útil cuando distintas pantallas/clientes necesitan subconjuntos muy distintos de los mismos datos.

No. El servidor reconoce la clave ya vista, y en vez de volver a ejecutar procesar_pago, devuelve la respuesta guardada de la primera ejecución. La clave la genera el cliente una vez por operación lógica — reintentar con ella es "pregunta por el resultado de esa operación", no "haz otra operación nueva".


Volver al: README.md · Relacionado: 00-fundamentos-backend.md, B-redis-cache-colas.md, 11-arquitectura.md