REST frente a GraphQL: Las verdaderas concesiones detrás de cada arquitectura
Explica los problemas específicos que resuelven REST y GraphQL, sus mecanismos internos, y los compromisos ocultos que hay que considerar antes de elegir uno para su API.
Fuentes: REST vs GraphQL: Which Problem Does Each One Actually Solve?
Cuando uno se pone a diseñar cómo deberían comunicarse un cliente y un servidor, casi siempre surge una pregunta: ¿REST o GraphQL?
Los debates sobre este tema tienden a presentarlo como una competencia, como si uno de los lados tuviera que ganar. Algunos desarrolladores sostienen que REST ya ha cumplido su función y debería ser abandonado. Otros insisten en que GraphQL introduce capas de complejidad que la mayoría de los proyectos no necesitan. Ninguna de estas perspectivas es particularmente útil.
Las tecnologías no están diseñadas para sumar puntos en una discusión. Existen para resolver problemas concretos con los que se enfrentan los desarrolladores en circunstancias específicas.
Adoptar GraphQL simplemente porque se considera la opción actual puede hacer que uno tenga que lidiar con problemas de caché y consultas costosas en el lado del servidor que nunca había previsto. Por otro lado, forzar una estructura rígida de REST en una aplicación móvil con grandes volúmenes de datos puede sobrecargar las pantallas con una serie de solicitudes de red secuenciales.
Para tomar una decisión acertada, es útil dejar de lado el alboroto mediático y analizar las motivaciones subyacentes: qué problema se intentaba resolver con cada enfoque, cómo funciona internamente y qué compromisos implica.
¿Qué problema resolvió REST?
Antes de que REST se convirtiera en la norma, los servicios web dependían en gran medida de protocolos como SOAP y XML-RPC. Estos requerían esquemas XML rígidos, envoltorios de mensajes extensos y una configuración compleja. Los clientes necesitaban conocer los nombres exactos de las procedimientos remotos solo para obtener datos.
Roy Fielding introdujo REST (Representational State Transfer) en el año 2000.
En esencia, REST ofrecía un concepto sencillo pero eficaz: aprovechar la propia arquitectura de la web.
En lugar de tratar un servidor como un motor opaco para la ejecución de funciones, REST modela un sistema como una colección de recursos, cada uno accesible a través de una URL clara. Las acciones sobre estos recursos se expresan mediante verbos HTTP estándar (GET, POST, PUT, DELETE), mientras que el resultado de cada solicitud se indica mediante códigos de estado HTTP conocidos, como 200 OK para éxitos, 404 Not Found cuando falta un recurso, o 500 Server Error cuando ocurre algún problema en el backend.
Resource-Oriented Architecture (REST)
Client ─── GET /api/users/12 ──────────> Server (Find user #12)
Client <── 200 OK [ { user data } ] ──── Server
Client ─── DELETE /api/posts/99 ───────> Server (Delete post #99)
Client <── 204 No Content ───────────── Server
Por qué es importante REST
- Simplicidad y previsibilidad: Una vez que comprendes que
/api/productsse refiere a una colección de productos y/api/products/42apunta a un elemento específico, la interfaz se vuelve autoexplicativa.
GET son idempotentes y se mapean a URLs únicas, infraestructuras como navegadores, CDNs (por ejemplo Cloudflare) y proxies inversos (como Nginx) pueden cachear las respuestas de forma automática, sin necesidad de lógica adicional en la aplicación.Este modelo funcionó bien para los desarrolladores durante mucho tiempo. Pero a medida que las aplicaciones cliente se volvieron más sofisticadas, comenzaron a surgir ciertos problemas.
Nuevos puntos problemáticos: sobrecarga de datos y falta de datos
Imagínese una aplicación móvil que muestra la pantalla del perfil de un usuario. Esa pantalla necesita exactamente tres cosas:
- El nombre del usuario
- Una URL que apunte al avatar del usuario
- Los títulos de los tres artículos más recientes del blog del usuario
Con una API REST convencional, satisfacer este requisito sencillo suele generar dos problemas distintos.
1. Sobreobtención de datos
El cliente solicita GET /api/users/12.
El servidor responde con el registro completo del usuario:
{
"id": 12,
"name": "Sarah Connor",
"avatarUrl": "https://cdn.example.com/sarah.jpg",
"email": "sarah@example.com",
"address": { "street": "...", "zip": "...", "country": "..." },
"billingHistory": [ ... ],
"preferences": { ... },
"createdAt": "2021-04-12T08:14:00Z"
}
La pantalla solo necesitaba name y avatarUrl, pero al final se cargaron detalles de dirección no relacionados, información de facturación y configuraciones de preferencias. Con una conexión celular lenta, transmitir bytes innecesarios agota la batería y ralentiza la renderización. Este escenario se conoce como sobreobtención de datos.
2. Subobtención de datos (el problema de red N+1)
El cliente aún necesita los artículos de Sarah, pero la respuesta de /api/users/12 no los incluía.
Por lo tanto, se realiza una segunda llamada: GET /api/users/12/posts.
Supongamos que ese endpoint devuelve solo una lista simple de identificadores de publicaciones. En ese caso, el cliente podría tener que realizar tres llamadas separadas: una para obtener /api/posts/101, otra para buscar /api/posts/102 y una tercera para recuperar /api/posts/103.
A este punto, el dispositivo ya ha realizado varias idas y venidas por la red de forma secuencial solo para mostrar una pantalla. Este patrón se denomina under-fetching.
¿Qué problema resolvió realmente GraphQL?
Facebook se topó exactamente con este cuello de botella alrededor del año 2012, mientras pasaba su aplicación principal de versiones basadas en la web móvil a aplicaciones nativas. Las conexiones móviles eran poco fiables y lentas, y la interfaz de Facebook estaba compuesta por datos estrechamente interconectados: usuarios, amigos, comentarios, me gusta y reacciones.
Diseñar un endpoint REST dedicado para cada pantalla, algo como /api/mobile-profile-screen, se convirtió rápidamente en un patrón inviable de gestionar.
Para solucionarlo, Facebook desarrolló GraphQL.
GraphQL no es una tecnología de base de datos. Es un lenguaje de consultas para APIs, combinado con un entorno de ejecución del lado del servidor que procesa las consultas contra un sistema de tipos definido por el usuario.
En lugar de realizar múltiples solicitudes a diferentes endpoints, el cliente envía una solicitud POST que contiene una consulta estructurada que especifica con exactitud el formato de los datos necesarios. El servidor, a su vez, devuelve un payload JSON que coincide perfectamente con ese formato.
El enfoque de GraphQL
Así es como esa misma pantalla de perfil solicitaría sus datos utilizando GraphQL:
GraphQL
query GetUserProfile {
user(id: "12") {
name
avatarUrl
posts(limit: 3) {
title
}
}
}
Y el servidor devuelve exactamente lo que se solicitó, nada más:
JSON
{
"data": {
"user": {
"name": "Sarah Connor",
"avatarUrl": "https://cdn.example.com/sarah.jpg",
"posts": [
{ "title": "Building Reliable Systems" },
{ "title": "Understanding Memory Leaks" },
{ "title": "A Guide to Clean Architecture" }
]
}
}
}
No hay uso inútil de ancho de banda debido a campos no utilizados, ni es necesario realizar una segunda o tercera solicitud para obtener datos relacionados. Una sola llamada de red proporciona exactamente los datos que la vista necesita para renderizarse.
Un vistazo al funcionamiento interno
Para comprender realmente las ventajas y desventajas involucradas, es útil observar cómo cada enfoque gestiona la ejecución en el lado del servidor.
El modelo basado en endpoints de REST
En un framework como Express.js, las rutas asignan directamente las URLs recibidas a las funciones de manejo:
JavaScript
// Express.js REST route
app.get('/api/users/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
El desarrollador de backend tiene control total sobre la lógica de consultas, las llamadas a la base de datos y la estructura de la respuesta. El ajuste del rendimiento es sencillo en este escenario: basta con dirigirse directamente al manejador del endpoint correspondiente y optimizar la sentencia SQL o agregar caché allí.
El modelo de esquema y resolutor de GraphQL
GraphQL se basa en un Esquema, junto con funciones individuales conocidas como Resolvers:
JavaScript
// Type definitions
const typeDefs = `
type Post {
id: ID!
title: String!
}
type User {
id: ID!
name: String!
avatarUrl: String!
posts: [Post]
}
type Query {
user(id: ID!): User
}
`;
// Resolvers map fields to actual data sources
const resolvers = {
Query: {
user: async (_, { id }) => {
return await db.users.findById(id);
},
},
User: {
posts: async (parentUser) => {
// Runs when the client query includes the 'posts' field
return await db.posts.findByAuthorId(parentUser.id);
},
},
};
Cuando llega una consulta, el motor de GraphQL la verifica contra el esquema y luego recorre los resolvers, ejecutándolos en un patrón similar al de un árbol.
Esto brinda a los equipos front-end mucha libertad para dar forma a sus solicitudes. Pero hay un detalle importante que cabe señalar: el cliente determina la estructura de la consulta, pero es el servidor quien debe asumir el costo de satisfacerla.
Qué costos implica GraphQL en segundo plano
Esa flexibilidad no es gratuita. Pasar de REST a GraphQL traslada efectivamente la complejidad del cliente al servidor.
El problema de las consultas N+1 en la capa de base de datos
Considere nuevamente la configuración del resolver anterior. ¿Qué sucede si un cliente solicita una lista de 20 usuarios junto con las publicaciones de cada uno?
GraphQL
query {
users {
name
posts {
title
}
}
}
- El resolver
usersse ejecuta una sola vez, obteniendo las 20 filas de una sola vez (SELECT * FROM users). - Luego, el resolver
postsse ejecuta por separado para cada usuario individual que se devuelve. - El resultado final: su base de datos termina manejando 1 consulta para la lista de usuarios más 20 consultas adicionales para las publicaciones, es decir, un total de 21 consultas.
Con REST, normalmente se combinaría todo esto en una sola unión SQL dentro del código del endpoint (SELECT * FROM users JOIN posts...). Con GraphQL, se necesitan herramientas de agrupamiento como DataLoader para recopilar estas consultas dispersas y fusionarlas antes de que lleguen a la base de datos.
Los problemas con el caché HTTP
Considere una llamada REST como esta:
GET /api/articles/42
Un CDN puede almacenar en caché esa respuesta durante, digamos, 10 minutos. Si 5,000 usuarios acceden a /api/articles/42 durante ese tiempo, su servidor origen solo necesita procesar al primero; el CDN se encarga del resto.
GraphQL rompe este patrón porque casi todas las operaciones se envían como una solicitud POST a un único endpoint, /graphql.
Dado que la consulta real se encuentra dentro del cuerpo de la solicitud y no en la URL, los cachés HTTP habituales, proxies y CDNs no pueden detectar que dos solicitudes están obteniendo datos idénticos. Para solucionarlo, se necesita un caché normalizado y sofisticado en el cliente (como Apollo Client o Relay) o una pasarela de caché diseñada específicamente para ello.
La profundidad de la consulta como vector de ataque
Con REST, no existe una forma sencilla para que un atacante haga que un único punto de extremo permita navegar por datos relacionales de profundidad arbitraria.
No obstante, con GraphQL, un actor malintencionado podría enviar algo como esto:
GraphQL
query EvilQuery {
user {
posts {
author {
posts {
author {
posts {
title
}
}
}
}
}
}
}
Sin protecciones como la evaluación de la complejidad de las consultas o la limitación de profundidad, una consulta como esta puede ralentizar enormemente su base de datos o agotar la memoria disponible del servidor.
REST y GraphQL uno al lado del otro
FeatureRESTGraphQLObtención de datosDevuelve una estructura fija de datos para cada URLDevuelve exactamente los campos que solicita el clienteLlamadas a la redA menudo requiere varios viajesIdiomaticamente se resuelve en una sola solicitudCaché HTTPIncorporado, sencillo y eficienteDifícil de implementar; necesita herramientas adicionales en el cliente o la pasarelaCurva de aprendizajeBaja, se basa en convenciones HTTP conocidasMás pronunciada, requiere comprensión de esquemas y resolversReporte de erroresCódigos de estado HTTP convencionales (404, 500, 401)Casi siempre 200 OK, con los problemas listados en un campo errorsSubida de archivosSencilla a través de datos de formulario multipartRequiere lógica de mutación personalizada o URLs de subida prefirmadasUso idealServicios de tipo CRUD, microservicios, APIs web abiertasUIs y aplicaciones complejas para varias plataformas
Hacer la llamada: cómo decidir
Cuándo REST es la mejor opción
- Su modelo de recursos es sencillo. Para productos típicos basados en operaciones CRUD como paneles de administración, tableros de control o tiendas virtuales básicas, REST le permite entregar resultados más rápidamente.
- El caché es muy importante para usted. Para contenido público con mucho tráfico de lectura, como blogs, páginas de noticias o catálogos, el caché HTTP nativo a través de CDNs es insuperable en términos de velocidad y costo.
- Está publicando una API para usuarios externos. Los desarrolladores externos ya pueden acceder a los puntos finales REST con
curlofetchsin necesidad de aprender una biblioteca de cliente dedicada.
Cuando GraphQL tiene más sentido
- Soporta muchos tipos diferentes de clientes. Imagine un producto con clientes para iOS, Android, relojes inteligentes y la web, cada uno de los cuales necesita un subconjunto diferente de los mismos datos subyacentes.
Conclusión
Ninguna de estas tecnologías ha quedado obsoleta por la otra.
- REST satisfizo la necesidad de una forma consistente, escalable y adecuada para el caché para exponer recursos a través de la web.
- GraphQL atendió la necesidad de que los clientes diseñen sus propias solicitudes de datos, reduciendo los viajes innecesarios a través de conexiones móviles lentas o poco fiables.
Elegir entre ellos no se trata de seguir tendencias, sino de comprender los verdaderos cuellos de botella que enfrenta su sistema. Si sus mayores desafíos son el rendimiento en dispositivos móviles y una lista cada vez más larga de puntos finales específicos para el frontend, GraphQL puede ser una solución sólida. Si lo que más valora es la simplicidad, un caché fiable y un backend fácil de mantener, REST sigue siendo una opción sólida y probada.