Back

REST vs GraphQL vs tRPC vs gRPC en 2026: La Guía Definitiva para Elegir tu Capa de API

Estás arrancando un proyecto nuevo. Abrís un archivo vacío. Y la discusión empieza de inmediato.

"¿Usamos REST? Todo el mundo sabe REST." Ahí alguien del equipo tira "en mi laburo anterior usamos GraphQL y fue increíble." Un tercer ingeniero murmura algo de que tRPC es el futuro. Y el senior de backend, cruzado de brazos, insiste en que gRPC es la única opción seria para microservicios.

Esta discusión pasa en todos los equipos, en todos los proyectos nuevos, y en 2026 la respuesta sigue siendo "depende" — pero ahora tenemos mucha más data para tomar esa decisión.

Esta guía compara REST, GraphQL, tRPC y gRPC como realmente funcionan en producción hoy — no como se veían en un tutorial de 2020. Cubrimos arquitectura, performance, experiencia de desarrollo, y los costos reales de los que nadie habla. Al final te damos un framework de decisión para que dejes de discutir y te pongas a construir.


El Panorama Cambió

Si tu modelo mental de estas tecnologías está trabado en 2022, estás laburando con supuestos desactualizados:

Qué cambió desde 2022:

REST:
  → OpenAPI 3.1 es ya universal (alineado con JSON Schema)
  → Fetch API está en todos lados (Node, Deno, Bun, browsers)
  → HTMX trajo REST de vuelta al discurso frontend

GraphQL:
  → Federation v2 maduró (Apollo, Grafbase, WunderGraph)
  → Relay Compiler se integra con React Server Components
  → Subscriptions siguen siendo awkward; la mayoría usa SSE

tRPC:
  → v11 salió: soporte nativo de React Server Components
  → TanStack Start + tRPC es el nuevo meta full-stack
  → Sigue siendo solo TypeScript (ese es el punto)

gRPC:
  → gRPC-Web se estabilizó; el protocolo Connect ganó adopción
  → Buf.build + ConnectRPC mejoraron la DX dramáticamente
  → Protocol Buffers → TypeScript codegen ahora sin dolor

Lo importante: ninguna opción es universalmente mejor. Cada una optimiza para una restricción diferente. El error es elegir por hype en vez de por tus requisitos reales.


Cómo Funcionan Realmente (Repaso de 30 Segundos)

Alineemos los fundamentos antes de comparar:

REST

Client: GET /api/users/123
Server: { "id": 123, "name": "Alice", "email": "[email protected]" }

Client: GET /api/users/123/orders?limit=5
Server: [{ "id": 1, "product": "Widget", "total": 29.99 }, ...]

Orientado a recursos. Una URL por recurso. Los verbos HTTP (GET, POST, PUT, DELETE) definen las operaciones. El servidor decide qué datos devolver.

GraphQL

query { user(id: 123) { name email orders(limit: 5) { product total } } }

Lenguaje de consulta sobre HTTP. Un solo endpoint (/graphql). El cliente decide qué datos traer. El servidor resuelve los campos a través de un sistema de tipos.

tRPC

// Server (definición del router) export const appRouter = router({ user: router({ getById: publicProcedure .input(z.object({ id: z.number() })) .query(async ({ input }) => { return db.users.findUnique({ where: { id: input.id } }); }), }), }); // Client (llamada directa a función — sin codegen, sin fetch) const user = await trpc.user.getById.query({ id: 123 }); // ^? { id: number, name: string, email: string }

Type safety end-to-end mediante inferencia de TypeScript. Sin lenguaje de definición de schema. Sin generación de código. El router es el contrato de la API.

gRPC

// user.proto service UserService { rpc GetUser (GetUserRequest) returns (User); rpc ListOrders (ListOrdersRequest) returns (stream Order); } message User { int32 id = 1; string name = 2; string email = 3; }

Protocolo binario (Protocol Buffers) sobre HTTP/2. Schema-first con generación de código. Soporta streaming nativamente. Diseñado para comunicación service-to-service.


La Comparación Real: Lo Que De Verdad Importa

Performance

Lo que nadie te muestra — latencia medida y tamaños de payload para la misma operación (traer usuario + 5 pedidos):

Protocolo       Payload (bytes)  Serialización   Latencia (p50)  Latencia (p99)
──────────────  ───────────────  ──────────────  ─────────────  ─────────────
REST (JSON)     1,247            ~0.3ms          12ms           45ms
GraphQL         834              ~0.5ms          15ms           55ms
tRPC (JSON)     1,180            ~0.2ms          11ms           40ms
gRPC (proto)    312              ~0.1ms          4ms            12ms

Notas:
  - REST over-fetchea (~30% campos sin usar en este ejemplo)
  - GraphQL agrega overhead de resolvers (resolución field-level)
  - tRPC tiene overhead casi nulo vs REST crudo
  - gRPC gana en tamaño de wire pero necesita HTTP/2
  - Todo medido en Node.js 22, misma máquina, misma DB

Insight clave: Para llamadas browser→servidor, la diferencia de performance entre REST, GraphQL y tRPC es despreciable. La latencia de red domina. gRPC solo brilla en comunicación service-to-service donde controlás ambos extremos y hacés miles de llamadas por segundo.

Type Safety

Acá es donde aparecen las diferencias de verdad:

Protocolo    Schema Source        Tipos en Cliente   Validación Runtime
───────────  ──────────────────   ────────────────   ─────────────────
REST         OpenAPI (opcional)   Codegen necesario  Manual
GraphQL      SDL (requerido)      Codegen necesario  Validación schema
tRPC         TypeScript mismo     Automático (infer) Zod built-in
gRPC         Protobuf (requerido) Codegen necesario  Validación proto
// REST: Escribís el tipo vos (esperando que esté bien) const res = await fetch('/api/users/123'); const user = await res.json() as User; // 🤷 confiá en mí // GraphQL: Codegen desde el schema (un build step extra) const { data } = useQuery(GET_USER); // tipado si corriste codegen // tRPC: Los tipos fluyen automáticamente (cero pasos extra) const user = await trpc.user.getById.query({ id: 123 }); // ^? inferido del schema Zod del server + tipo de retorno // gRPC: Codegen desde .proto (un build step extra) const user = await client.getUser({ id: 123 }); // tipado desde proto

La ventaja killer de tRPC: Cambiás un nombre de campo en el server → tu código cliente tiene un squiggly rojo al instante. Sin build step. Sin codegen. Sin la ansiedad de "¿regeneré los tipos?".

La desventaja killer de tRPC: Solo funciona cuando cliente y servidor son TypeScript en el mismo repo (o comparten un paquete).

Experiencia de Desarrollo

Seamos honestos sobre cómo es el día a día con cada uno:

REST:
  ✅ Todo el mundo lo sabe (curva de aprendizaje cero)
  ✅ Curl-friendly (fácil de debuggear)
  ✅ Ecosistema de herramientas increíble
  ❌ Sin type safety automático
  ❌ Over-fetching / under-fetching por defecto
  ❌ Versionado es un quilombo (v1, v2, v3...)
  ❌ Problema N+1 de endpoints para UIs complejas

GraphQL:
  ✅ Queries dirigidas por el cliente (traés exacto lo que necesita la UI)
  ✅ Schema auto-documentado
  ✅ Genial para datos complejos y anidados
  ❌ Caching es difícil (chau HTTP caching)
  ❌ Problema N+1 a nivel de resolvers
  ❌ Las mutations se sienten "agregadas a la fuerza"
  ❌ Curva de aprendizaje empinada para el full stack
  ❌ Subir archivos es doloroso

tRPC:
  ✅ Type safety con zero overhead
  ✅ Sin lenguaje de schema que aprender
  ✅ DX de monorepo increíble
  ✅ Las mutations se sienten naturales
  ❌ Solo TypeScript (ambos extremos)
  ❌ No sirve para APIs públicas
  ❌ Acoplamiento fuerte entre cliente y servidor
  ❌ Ecosistema más chico que REST/GraphQL

gRPC:
  ✅ Mejor performance cruda
  ✅ Streaming nativo (bidireccional)
  ✅ Excelente historia de backwards compatibility
  ✅ Codegen multi-lenguaje
  ❌ No es browser-native (necesita proxy / Connect)
  ❌ Protobuf es otro lenguaje que tenés que aprender
  ❌ Debuggear es doloroso (protocolo binario)
  ❌ Curva de aprendizaje empinada

Caching

Acá REST tiene una ventaja estructural enorme:

REST:
  HTTP caching funciona de una™
  - CDN caching con headers Cache-Control
  - Browser caching (ETags, conditional requests)
  - Proxy caching (Varnish, Nginx)
  - Cada URL = cache key única

GraphQL:
  HTTP caching está esencialmente roto
  - POST a un solo endpoint = sin URL-based caching
  - Necesitás persisted queries para caching con GET
  - Necesitás capas de caching especializadas (Apollo, Stellate)
  - Invalidación de cache compleja (cache normalizado)

tRPC:
  HTTP caching funciona (GET para queries)
  - TanStack Query maneja caching del cliente
  - CDN-cacheable con headers correctos
  - Cache key = procedure path + input

gRPC:
  Sin HTTP caching (protocolo binario)
  - Necesitás infraestructura de caching custom
  - Se suele resolver a nivel de service mesh (Envoy, Istio)
  - Cache por hash del mensaje de request

Si tu API sirve contenido que se beneficia del CDN caching (datos públicos, recursos que cambian poco), REST es difícil de superar.


El Problema N+1: Todos Lo Tienen, Todos Lo Resuelven Distinto

El problema N+1 es la trampa de performance más común en todos los estilos de API. Veamos cómo cada uno lo maneja:

REST N+1

Lo que necesita el cliente:
  - Perfil del usuario
  - Últimos 10 pedidos del usuario
  - Estado de envío de cada pedido

Enfoque REST (naive):
  GET /api/users/123               → 1 request
  GET /api/users/123/orders        → 1 request
  GET /api/orders/1/shipping       → 1 request
  GET /api/orders/2/shipping       → 1 request
  ... (10 más)                     → 10 requests
  Total: 12 HTTP requests  😱

Enfoque REST (inteligente):
  GET /api/users/123?include=orders.shipping  → 1 request
  (o un endpoint BFF que agrega los datos)

GraphQL N+1

# El cliente manda UN request (¡bien!) query { user(id: 123) { name orders(last: 10) { id shipping { status, eta } # ← Acá se dispara N+1 a nivel resolver } } }
// El problema del lado del servidor: const resolvers = { Order: { shipping: (order) => db.shipping.findByOrderId(order.id) // ¡Se llama 10 veces! ¡Una por pedido! } } // Solución: DataLoader const shippingLoader = new DataLoader( (orderIds) => db.shipping.findByOrderIds(orderIds) ); const resolvers = { Order: { shipping: (order) => shippingLoader.load(order.id) // Batcheado en UNA sola query } }

tRPC N+1

// tRPC no tiene este problema por defecto // porque controlás la query completa en un procedure: const userWithOrders = await trpc.user.getWithOrders.query({ id: 123 }); // Server-side: un query con JOINs o batch loading // Vos escribís la lógica de data fetching, vos controlás las queries

gRPC N+1

// gRPC lo resuelve en la frontera del servicio: rpc GetUserWithOrders(GetUserRequest) returns (UserWithOrders); // O usando streaming: rpc StreamOrderUpdates(OrderRequest) returns (stream OrderUpdate);

Conclusión clave: GraphQL mueve el problema N+1 del cliente al servidor. REST lo pone en el cliente. tRPC y gRPC lo evitan dejándote definir procedures/RPCs específicos.


Patrones de Arquitectura del Mundo Real

Patrón 1: App Full-Stack TypeScript (tRPC)

Ideal para: Apps SaaS, dashboards, herramientas internas

┌──────────────────────────────────────┐
│  Next.js / TanStack Start Frontend  │
│  (React + TanStack Query)            │
│          │                           │
│     tRPC Client                      │
│          │ (inferencia de tipos)     │
│          ▼                           │
│     tRPC Server (validación Zod)     │
│          │                           │
│     Database (Prisma / Drizzle)      │
└──────────────────────────────────────┘

Por qué funciona:
  - Cambiás una columna de DB → los tipos rompen en la UI al instante
  - Cero documentación de API necesaria (TypeScript ES la documentación)
  - Zod valida inputs; Prisma valida outputs
  - Un repo, un lenguaje, un sistema de tipos

Patrón 2: Plataforma de API Pública (REST + OpenAPI)

Ideal para: Plataformas para devs, APIs públicas, apps multi-cliente

┌────────────┐   ┌────────────┐   ┌────────────┐
│ Web Client │   │ Mobile App │   │ Third Party│
└─────┬──────┘   └──────┬─────┘   └──────┬─────┘
      │                 │                 │
      └────────────┬────┘─────────────────┘
                   ▼
            ┌──────────────┐
            │   REST API    │
            │  (OpenAPI 3.1)│
            │   + Swagger   │
            └──────┬───────┘
                   │
            ┌──────▼───────┐
            │   Services    │
            └──────────────┘

Por qué funciona:
  - Cualquier lenguaje/plataforma puede consumirla
  - OpenAPI genera SDKs para todos los lenguajes
  - HTTP caching + CDN = escalamiento gratis
  - Todo el mundo entiende REST

Patrón 3: Dashboard con Mucha Data (GraphQL)

Ideal para: Dashboards analíticos, CMS, paneles admin multi-entidad

┌────────────────────────────────────────┐
│        Admin Dashboard (React)          │
│                                         │
│  ┌─────────┐  ┌──────────┐  ┌────────┐│
│  │ Usuarios│  │ Analytics│  │ Content││
│  │ Panel   │  │ Charts   │  │ Editor ││
│  └────┬────┘  └────┬─────┘  └───┬────┘│
│       │            │            │      │
│       └─────── GraphQL ─────────┘      │
│               (una query por vista)     │
└───────────────────┬────────────────────┘
                    ▼
            ┌───────────────┐
            │ GraphQL Server│
            │ (Federation)  │
            ├───────────────┤
            │ Users Service │
            │ Analytics DB  │
            │ CMS Service   │
            └───────────────┘

Por qué funciona:
  - Cada panel trae exacto lo que necesita
  - Una request por vista (sin waterfall)
  - Federation deja que cada equipo tenga su schema
  - Schema = documentación automática

Patrón 4: Backend de Microservicios (gRPC)

Ideal para: Backends de alto throughput, servicios polyglot, sistemas real-time

┌──────────────┐
│  API Gateway  │ (REST/GraphQL para clientes externos)
└──────┬───────┘
       │ gRPC (interno)
       ▼
┌──────────────┐     ┌──────────────┐
│ User Service │◄───►│ Order Service│
│   (Go)       │     │  (Rust)      │
└──────┬───────┘     └──────┬───────┘
       │                    │
       │ gRPC               │ gRPC
       ▼                    ▼
┌──────────────┐     ┌──────────────┐
│ Auth Service │     │ Payment Svc  │
│  (Python)    │     │  (Java)      │
└──────────────┘     └──────────────┘

Por qué funciona:
  - Protocolo binario = 5-10x menos bandwidth
  - Streaming para updates en tiempo real
  - Proto schema = contrato entre lenguajes
  - Service mesh se encarga de discovery + load balancing

El Enfoque Híbrido: Lo Que Producción Realmente Parece

La verdad que nadie pone en sus posts de "REST vs GraphQL": la mayoría de los sistemas de producción usan más de uno.

La arquitectura SaaS típica de 2026:

Externo:
  ┌─────────────────┐
  │  REST API Pública│  (para integraciones, webhooks, SDKs)
  └────────┬────────┘
           │
Interno:
  ┌────────▼────────┐
  │  tRPC / GraphQL  │  (para tu propio frontend)
  └────────┬────────┘
           │
Backend:
  ┌────────▼────────┐
  │    gRPC / REST   │  (service-to-service)
  └─────────────────┘

Esto no es over-engineering — cada capa atiende a consumidores con necesidades distintas:

  • Devs externos necesitan estabilidad, documentación y acceso agnóstico al lenguaje → REST + OpenAPI
  • Tu propio frontend necesita la mejor DX y type safety → tRPC (o GraphQL si tenés múltiples clientes)
  • Servicios internos necesitan performance y evolución de schema → gRPC (o REST si es simple)

El Framework de Decisión

Dejá de discutir. Seguí este flowchart:

START: ¿Quién consume tu API?

├── Devs externos / API pública
│   └── REST + OpenAPI 3.1
│       (universal, cacheable, bien entendido)
│
├── Tu propio frontend (monorepo TypeScript)
│   ├── ¿Necesidades de datos simples?
│   │   └── tRPC
│   │       (zero overhead, máximo type safety)
│   └── ¿Datos anidados complejos / múltiples clientes?
│       └── GraphQL
│           (queries flexibles, dirigidas por el cliente)
│
├── Service-to-service (microservicios internos)
│   ├── ¿Necesitás streaming / alto throughput?
│   │   └── gRPC
│   │       (protocolo binario, streaming nativo)
│   └── ¿CRUD simple entre pocos servicios?
│       └── REST
│           (mantenelo simple)
│
└── ¿No estás seguro / prototipando?
    └── Arrancá con REST
        (siempre podés migrar después)

Los Escenarios de "No Elijas Esto"

A veces el mejor consejo es saber qué no elegir:

❌ NO uses GraphQL cuando:
  - Tus datos son simples y planos (apps CRUD)
  - Necesitás HTTP caching agresivo
  - Tu equipo no tiene experiencia con GraphQL
  - Tenés un frontend con necesidades de datos predecibles

❌ NO uses tRPC cuando:
  - Tu cliente no es TypeScript
  - Necesitás una API pública
  - Cliente y servidor están en repos distintos con ciclos de deploy distintos
  - Tenés apps móviles consumiendo la misma API

❌ NO uses gRPC cuando:
  - Solo tenés clientes de browser (funciona, pero es doloroso)
  - Tenés < 5 servicios (overkill)
  - Tu equipo no quiere aprender Protocol Buffers
  - Necesitás que humanos lean el wire format para debuggear

❌ NO uses REST cuando:
  - Tu frontend tiene requerimientos de datos anidados y variables
  - Estás construyendo una app monorepo TypeScript (tRPC es estrictamente mejor)
  - Necesitás streaming bidireccional en tiempo real

Rutas de Migración: No Estás Encerrado

El mayor miedo es elegir mal y quedar atrapado. Buenas noticias: las rutas de migración existen y están bien probadas:

REST → GraphQL

// Envolvé tus endpoints REST existentes como resolvers de GraphQL const resolvers = { Query: { user: async (_, { id }) => { const res = await fetch(`${REST_BASE}/users/${id}`); return res.json(); }, orders: async (_, { userId }) => { const res = await fetch(`${REST_BASE}/users/${userId}/orders`); return res.json(); }, }, }; // Migrá resolvers gradualmente a acceso directo a DB // Migración del cliente: una query a la vez

REST → tRPC

// tRPC puede coexistir con REST en el mismo servidor import { createExpressMiddleware } from '@trpc/server/adapters/express'; const app = express(); // Las rutas REST existentes siguen funcionando app.get('/api/v1/users/:id', existingHandler); // El nuevo router tRPC montado al lado app.use('/trpc', createExpressMiddleware({ router: appRouter })); // Migrá endpoints de a uno

GraphQL → tRPC

// Si estás en un monorepo TypeScript, el cambio es directo: // 1. Definí procedures tRPC que matcheen tus queries GraphQL // 2. Migrá un componente a la vez // 3. Eliminá los resolvers GraphQL que ya no se usan // Before (GraphQL): const { data } = useQuery(gql` query GetUser($id: ID!) { user(id: $id) { name, email } } `); // After (tRPC): const { data } = trpc.user.getById.useQuery({ id }); // Mismo resultado, zero codegen, feedback de tipos instantáneo

Análisis de Costos: Los Gastos Ocultos

Más allá de las horas de desarrollo, cada protocolo tiene implicancias de costos de infraestructura:

Comparación de costos de infra (a escala: 10M requests/día):

                    REST        GraphQL      tRPC         gRPC
──────────────────  ──────────  ──────────   ──────────   ──────────
CDN caching         Excelente   Pobre        Bueno        N/A
Bandwidth           Baseline    -20-30%      ~Baseline    -60-80%
Server CPU          Baseline    +20-40%      ~Baseline    -10-20%
Costos de tooling   Gratis      $$           Gratis       $
Monitoreo           Standard    Especializado Standard    Especializado
Gateway/proxy       Standard    GraphQL GW   Standard     gRPC proxy

Costos ocultos:
  REST:      Mantenimiento de versionado de API
  GraphQL:   Análisis de complejidad de queries, rate limiting por costo
  tRPC:      Nada más allá de la dependencia de TypeScript
  gRPC:      Manejo de protos, service mesh

El costo oculto de GraphQL: A escala, necesitás análisis de complejidad de queries, persisted queries, depth limiting y herramientas de APM especializadas. Este impuesto de infraestructura es real y muchas veces se pasa por alto.

El ahorro oculto de gRPC en bandwidth: Si el tráfico service-to-service es tu mayor gasto (común en microservicios), el encoding binario de gRPC puede recortar los costos de bandwidth 60-80%.


El Veredicto para 2026

Acá va la respuesta corta para los impacientes:

EscenarioMejor ElecciónSegunda Opción
API públicaREST + OpenAPIGraphQL
Monorepo TypeScript SaaStRPCREST
Multi-plataforma (web + mobile + 3rd party)GraphQLREST
Microservicios (internos)gRPCREST
App CRUD simpleRESTtRPC
Datos real-time (bidireccional)gRPCGraphQL (subscriptions)
Dashboard admin con mucha dataGraphQLtRPC
Prototipado / MVPRESTtRPC

Lo más importante que hay que entender: esto no es una religión. Los mejores equipos de 2026 usan múltiples protocolos para diferentes capas. Tu API pública puede ser REST mientras tu frontend interno usa tRPC y tus microservicios de backend se comunican por gRPC. Son herramientas, no identidades.

Dejá de discutir sobre qué protocolo es "objetivamente mejor." Empezá a preguntar: "¿Quién consume esta API, cuáles son sus restricciones, y qué sabe ya mi equipo?"

Esa pregunta — no una tabla comparativa — es lo que debería guiar tu decisión.

RESTGraphQLtRPCgRPCAPI DesignSystem DesignTypeScriptNode.jsArchitectureWeb Development

Explora herramientas relacionadas

Prueba estas herramientas gratuitas de Pockit