El error de plantear "REST o GraphQL" como una guerra de bandos

Cada vez que alguien me pregunta «¿deberíamos pasarnos a GraphQL?», la respuesta corta es: depende de quién va a consumir la API y de qué problema te está doliendo hoy. REST y GraphQL no son dos versiones de lo mismo — resuelven problemas distintos, y en mi día a día con Magento y Laravel acabo usando los dos, a veces en el mismo proyecto.

El error que veo más a menudo es elegir GraphQL porque «es lo moderno» para una API que solo va a consumir un backoffice interno, o quedarse con REST por costumbre cuando el frontend necesita combinar datos de cinco endpoints distintos en cada pantalla.

En resumen, esto es lo que miro antes de decidir:

  • ¿Quién consume la API? Un frontend propio con vistas variables → GraphQL tiene sentido. Integraciones con terceros o partners → REST es más fácil de documentar y depurar.
  • ¿Necesitas caching HTTP estándar (CDN, Varnish)? → REST. GraphQL complica el caching porque casi todo va por POST a un único endpoint.
  • ¿El cliente necesita combinar datos de varias entidades relacionadas en una sola pantalla? → GraphQL evita el over-fetching y el «endpoint a medida para cada vista».
  • ¿Es Magento headless (PWA Studio)? → GraphQL ya viene decidido por la arquitectura.
  • ¿El equipo ya tiene experiencia con GraphQL? → Si no, la curva de aprendizaje (resolvers, N+1, caching) tiene un coste real.

El resto del artículo explica el porqué de cada una, con ejemplos de Magento y Laravel.

Qué problema resuelve cada uno

REST modela la API como recursos (/orders, /orders/123, /customers/45) y cada endpoint devuelve una forma fija de datos. Es predecible, se beneficia de todo el ecosistema HTTP (caching, códigos de estado, herramientas de depuración) y cualquier desarrollador lo entiende sin documentación adicional.

GraphQL expone un único endpoint y deja que el cliente describa exactamente qué datos necesita, incluyendo relaciones anidadas, en una sola petición. Resuelve dos problemas muy concretos de REST: el over-fetching (el endpoint devuelve más campos de los que la vista necesita) y el under-fetching (la vista necesita encadenar varias llamadas para montar la pantalla).

Peculiaridades de REST

  • Versionado. Cambiar la forma de un recurso obliga a versionar (/v2/orders) o a aceptar que rompes a los clientes existentes.
  • Caching HTTP de serie. ETag, Cache-Control y un CDN o Varnish delante funcionan sin esfuerzo adicional — fundamental en eCommerce, donde el catálogo se sirve a miles de visitas anónimas.
  • Riesgo de under-fetching. Una pantalla de «detalle de pedido con cliente y líneas» puede acabar siendo 3 peticiones si no diseñas bien los endpoints.

En Laravel, esto se traduce en un API Resource por recurso:

// app/Http/Resources/OrderResource.php
class OrderResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->id,
            'reference' => $this->reference,
            'total' => $this->total,
            'status' => $this->status,
            'customer' => new CustomerResource($this->whenLoaded('customer')),
            'lines' => OrderLineResource::collection($this->whenLoaded('lines')),
        ];
    }
}

// routes/api.php
Route::apiResource('orders', OrderController::class);

Peculiaridades de GraphQL

  • Un único endpoint. Toda la API vive en /graphql, lo que simplifica el cliente pero complica el caching HTTP tradicional, ya que casi todo va por POST.
  • El problema N+1 se traslada al resolver. Si Order.customer y Order.lines se resuelven cada uno con su propia query, pedir 50 pedidos con cliente y líneas dispara cientos de queries si no usas batching (DataLoader o similar).
  • La seguridad cambia de forma. Ya no controlas el acceso por endpoint, sino por complejidad de query — alguien puede pedir relaciones anidadas varios niveles y tumbar la base de datos con una sola petición «válida».

En Laravel, la alternativa habitual es Lighthouse, que define el esquema de forma declarativa:

type Order {
    id: ID!
    reference: String!
    total: Float!
    status: OrderStatus!
    customer: Customer! @belongsTo
    lines: [OrderLine!]! @hasMany
}

type Query {
    order(id: ID! @eq): Order @find
    orders: [Order!]! @paginate
}

El caso Magento: la arquitectura ya decide buena parte

Magento 2 trae ambas APIs de serie, y en la práctica cada una tiene un rol distinto:

  • REST está pensada para integraciones backend: ERPs, PIMs, sincronización de pedidos y stock. Es la API que ya conocen los sistemas con los que vas a integrar, con autenticación por token de integración bien documentada.
  • GraphQL nació para PWA Studio y los storefronts headless: catálogo, carrito y checkout necesitan combinar producto, precio, stock y atributos configurables en una sola pantalla, y GraphQL evita encadenar media docena de llamadas REST por cada página de producto.
{
  products(filter: { sku: { eq: "24-MB01" } }) {
    items {
      name
      sku
      price_range {
        minimum_price {
          regular_price { value currency }
        }
      }
      ... on ConfigurableProduct {
        configurable_options { attribute_code values { label } }
      }
    }
  }
}

Si el proyecto es un storefront headless sobre Magento, GraphQL no es una opción que evalúas: viene dado. Si lo que montas es una integración con el ERP del cliente, REST sigue siendo el camino más simple y mejor soportado por el ecosistema.

El caso Laravel: API Resources vs Lighthouse

En un proyecto Laravel «normal» (panel de administración, backoffice, integración puntual), REST con API Resources es la opción por defecto: no añade dependencias, cualquiera en el equipo lo entiende, y el caching HTTP funciona sin configuración extra.

Lighthouse empieza a tener sentido cuando el consumidor es un frontend propio con vistas muy variables — por ejemplo, una app móvil donde la pantalla de listado necesita 5 campos por pedido pero la de detalle necesita 30, y no quieres mantener dos endpoints o un parámetro ?fields= casero que reinventa GraphQL a medias.

Lo que casi nunca compensa: montar GraphQL para una API que solo va a consumir tu propio backoffice interno, o para una integración con un tercero que espera REST. Ahí GraphQL añade una capa de complejidad — resolvers, N+1, autenticación de queries — sin que nadie se beneficie de su flexibilidad.

Las preguntas que me hago antes de elegir

  1. ¿Quién consume la API: un frontend propio o un tercero? → Frontend propio con vistas variables: GraphQL. Terceros e integraciones: REST.
  2. ¿Necesitas que un CDN o Varnish cacheen las respuestas? → REST. Cachear GraphQL bien requiere persisted queries y trabajo adicional.
  3. ¿Es Magento headless (PWA Studio) o un tema/checkout estándar? → Headless: GraphQL ya viene decidido. Estándar: REST para lo que necesites tocar desde fuera.
  4. ¿El equipo tiene experiencia con resolvers, N+1 y DataLoader? → Si no, cuenta ese aprendizaje como parte del coste del proyecto.
  5. ¿La API va a crecer con muchas vistas distintas del mismo dato? → Cuantas más combinaciones de campos necesites, más pesa GraphQL en la balanza.

Conclusión

REST y GraphQL no compiten por el mismo puesto. REST sigue siendo la opción más simple, más cacheable y más fácil de integrar con terceros — y por eso es mi punto de partida por defecto. GraphQL gana cuando el consumidor es un frontend propio con necesidades de datos muy variables, o cuando la plataforma, como Magento headless, ya lo trae integrado.

Si tu proyecto tiene Magento y Laravel en la misma conversación y no tienes claro por dónde tirar para la API, cuéntame en qué consiste.