Saltar al contenido

GraphQL vs REST: eligiendo el estilo de API correcto

GraphQL no reemplaza a REST: es una alternativa con compromisos distintos que importan según tu cliente, el tamaño del equipo y la forma de tus datos.

4 min de lectura
Comparación lado a lado de una query GraphQL y un endpoint REST para los mismos datos

El debate GraphQL vs. REST muchas veces genera más ruido que claridad. Los equipos adoptan GraphQL porque está de moda, y después batallan con el caching, la autorización y las queries N+1. Otros lo descartan por completo y construyen endpoints REST con 15 query parameters para evitar el over-fetching. La elección correcta depende de tus restricciones específicas — la diversidad de clientes, las relaciones de datos y la experiencia del equipo.

La diferencia central

REST organiza las APIs alrededor de recursos con formas de respuesta fijas. GraphQL le da a los clientes un lenguaje de consulta para pedir exactamente los datos que necesitan.

tstypescript
// REST — multiple requests, potentially over-fetching
// GET /api/users/123
// GET /api/users/123/orders?limit=5
// GET /api/users/123/reviews?limit=3
 
// Each endpoint returns its full schema, including fields the client doesn't use
 
// GraphQL — one request, exact data
const query = `
  query UserDashboard($userId: ID!) {
    user(id: $userId) {
      name
      avatar
      orders(limit: 5) {
        id
        total
        status
      }
      reviews(limit: 3) {
        rating
        comment
      }
    }
  }
`;

Con REST, el servidor decide qué datos devolver. Con GraphQL, el cliente decide. Esto traslada la complejidad del cliente al servidor.

Dónde gana REST

REST es más simple para operaciones CRUD directas con recursos bien definidos.

tstypescript
// ✅ REST excels at simple, resource-oriented APIs
// Clear, cacheable, easy to understand
app.get("/api/products/:id", getProduct);
app.post("/api/products", createProduct);
app.patch("/api/products/:id", updateProduct);
app.delete("/api/products/:id", deleteProduct);
 
// HTTP caching works out of the box
// GET /api/products/123
// Cache-Control: public, max-age=300
// ETag: "abc123"
tstypescript
// ❌ GraphQL adds complexity for simple operations
const CREATE_PRODUCT = gql`
  mutation CreateProduct($input: CreateProductInput!) {
    createProduct(input: $input) {
      id
      name
      price
    }
  }
`;
 
// Every request is POST, every response is 200
// HTTP caching doesn't work without additional infrastructure
// Error handling is an envelope format, not HTTP status codes

Ventajas de REST: caching HTTP, tooling más simple, semántica de códigos de estado, curva de aprendizaje más pequeña, compatibilidad con CDN.

Dónde gana GraphQL

GraphQL brilla cuando los clientes tienen necesidades de datos diversas — especialmente apps móviles con restricciones de ancho de banda o dashboards que agregan datos de múltiples dominios.

tstypescript
// ❌ REST — the mobile app needs 5 requests for one screen
const user = await fetch("/api/users/me");
const orders = await fetch("/api/users/me/orders?limit=3");
const notifications = await fetch("/api/notifications?unread=true");
const recommendations = await fetch("/api/recommendations?limit=5");
const stats = await fetch("/api/users/me/stats");
// 5 round trips, over-fetching on each response
 
// ✅ GraphQL — one request, exact data needed
const DASHBOARD = gql`
  query Dashboard {
    me {
      name
      avatar
    }
    myOrders(limit: 3) {
      id
      total
      status
    }
    notifications(filter: { unread: true }) {
      id
      message
    }
    recommendations(limit: 5) {
      id
      title
      image
    }
    myStats {
      totalOrders
      totalSpent
    }
  }
`;

Una solicitud reemplaza a cinco. El cliente obtiene exactamente los campos que necesita, nada más. En redes móviles, esto importa.

El problema N+1 en GraphQL

Las queries flexibles de GraphQL crean un desafío del lado del servidor: resolver campos anidados puede disparar cientos de queries a la base de datos.

tstypescript
// This innocent query triggers 1 + N database calls
// 1 query for users, then N queries for each user's orders
const query = `{
  users(limit: 50) {
    name
    orders {
      id
      total
    }
  }
}`;
tstypescript
// ✅ DataLoader batches and deduplicates database calls
import DataLoader from "dataloader";
 
const orderLoader = new DataLoader(async (userIds: string[]) => {
  // One query for ALL user orders, not one per user
  const orders = await db.orders.findMany({
    where: { userId: { in: userIds } },
  });
 
  // Group by userId and return in the same order as input
  const ordersByUser = new Map<string, Order[]>();
  for (const order of orders) {
    const list = ordersByUser.get(order.userId) ?? [];
    list.push(order);
    ordersByUser.set(order.userId, list);
  }
 
  return userIds.map((id) => ordersByUser.get(id) ?? []);
});
 
// In the resolver
const resolvers = {
  User: {
    orders: (user: User) => orderLoader.load(user.id),
  },
};

DataLoader es esencial para cualquier servidor GraphQL no trivial. Sin él, el rendimiento se degrada exponencialmente con la profundidad de la query.

Complejidad de la autorización

La autorización en REST es directa — el middleware verifica antes de que se ejecute el handler. La autorización en GraphQL debe ocurrir a nivel de campo, porque distintos campos pueden tener distintas reglas de acceso.

tstypescript
// REST — one auth check per endpoint
app.get("/api/users/:id", requireAuth, requireOwnerOrAdmin, getUser);
 
// GraphQL — auth at the field level
const resolvers = {
  User: {
    email: (user, args, context) => {
      // Only the user themselves or an admin can see email
      if (context.userId !== user.id && !context.isAdmin) return null;
      return user.email;
    },
    salary: (user, args, context) => {
      // Only HR or the user themselves
      if (context.userId !== user.id && !context.roles.includes("hr")) {
        throw new ForbiddenError("Cannot access salary");
      }
      return user.salary;
    },
  },
};

Esta autorización por campo es más granular pero más difícil de auditar. "¿Quién puede ver qué?" se convierte en una pregunta que requiere rastrear el código de los resolvers en lugar de simplemente revisar el middleware de rutas.

Marco de decisión

FactorElige RESTElige GraphQL
Diversidad de clientes1-2 clientes con necesidades similaresMúltiples clientes con necesidades de datos distintas
Forma de los datosPlana, orientada a recursosProfundamente anidada, relacional
Necesidades de cachingEl caching HTTP es importanteUn caching a medida es aceptable
Tamaño del equipoEquipo pequeño, API simpleEquipo más grande, capa de API dedicada
Necesidades de tiempo realSSE o WebSocket por separadoSubscriptions incorporadas
Consumidores de la APIDesarrolladores externosClientes internos que controlas

Puntos clave

  1. REST es más simple para CRUD orientado a recursos con buen soporte de caching HTTP
  2. GraphQL reduce el over-fetching cuando los clientes tienen necesidades de datos diversas y anidadas
  3. DataLoader es obligatorio en cualquier servidor GraphQL para evitar problemas de queries N+1
  4. GraphQL traslada la complejidad al servidor — la autorización, el caching y el rendimiento se vuelven más difíciles
  5. No elijas por moda — elige según la diversidad de tus clientes, la forma de tus datos y la experiencia de tu equipo
Wilfredo Rujel

Wilfredo Rujel

Ingeniero de Software Full Stack

Compartir esta publicaciónX