Diseñar un API Gateway Escalable para Microservicios
Construí un gateway de API que enrute requests, maneje autenticación, rate limiting, caching y traducción de protocolos entre clientes y microservicios backend.
Overview
En una arquitectura de microservicios, los clientes pueden terminar hablando con decenas de servicios distintos, cada uno con su propio endpoint, protocolo y reglas de auth. Exponer todo eso directamente genera un desastre: cada cliente tiene que rastrear ubicaciones, manejar retries y administrar tokens separados. Cuando un servicio se mueve o aparece uno nuevo, cada cliente necesita una actualización.
Me topé con este dolor en una plataforma con 14 microservicios. Los clientes mobile tenían hardcodeadas 14 base URLs y re-implementaban validación JWT en tres lenguajes. Cuando agregamos un API gateway, el equipo mobile borró 2,000 líneas de boilerplate de la noche a la mañana. Una URL, un check de auth, un solo lugar para poner concerns cross-cutting.
Un API gateway se ubica al frente y se convierte en el único punto de entrada. Los clientes llaman a una URL y el gateway reenvía el request al backend correcto. También maneja concerns cross-cutting — autenticación, rate limiting, terminación SSL y transformación de request/response — para que los servicios internos no tengan que hacerlo. Para el panorama completo de topología de microservicios, mirá nuestra recipe de microservices patterns.
Cuándo Usarlo
Un API gateway empieza a valer la pena cuando varios servicios backend están expuestos a clientes, cuando auth y rate limiting necesitan centralizarse, o cuando distintos tipos de clientes como web, mobile e IoT necesitan formas de API diferentes. También ayuda durante una migración de monolito a microservicios porque permite mantener estable el contrato externo mientras cambia la topología interna. Finalmente, es el lugar correcto para poner una API GraphQL por encima de microservicios REST.
Cuándo NO Usarlo
Un gateway es exceso para uno o dos servicios; un reverse proxy o load balancer simple maneja eso. Saltearlo si el equipo no está listo para operarlo como infraestructura crítica con HA y monitoreo, si la lógica de negocio se filtra al gateway, o si el proyecto es pequeño y el overhead operacional no se justifica.
Solución
Kong Gateway (declarativo)
# kong.yml
_format_version: "3.0"
services:
- name: user-service
url: http://users.internal:8080
routes:
- name: user-routes
paths:
- /api/v1/users
plugins:
- name: rate-limiting
config:
minute: 100
policy: redis
- name: jwt
config:
claims_to_verify:
- exp
- name: proxy-cache
config:
response_code:
- 200
request_method:
- GET
cache_ttl: 300
strategy: memory
- name: order-service
url: http://orders.internal:8080
routes:
- name: order-routes
paths:
- /api/v1/orders
Gateway custom en Node.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');
const jwt = require('jsonwebtoken');
const app = express();
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 100,
standardHeaders: true,
});
app.use('/api/', limiter);
app.use('/api/', (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) return res.status(401).json({ error: 'Missing token' });
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch {
res.status(401).json({ error: 'Invalid token' });
}
});
const services = {
'/api/v1/users': 'http://users.internal:8080',
'/api/v1/orders': 'http://orders.internal:8080',
};
Object.entries(services).forEach(([path, target]) => {
app.use(path, createProxyMiddleware({
target,
changeOrigin: true,
pathRewrite: { [`^${path}`]: '' },
onProxyReq: (proxyReq, req) => {
proxyReq.setHeader('X-User-Id', req.user.sub);
},
}));
});
app.get('/health', (req, res) => res.json({ status: 'ok' }));
app.listen(3000, () => console.log('Gateway running on port 3000'));
Traefik con labels de Docker
# docker-compose.yml
services:
user-service:
image: myregistry/user-service:latest
labels:
- "traefik.enable=true"
- "traefik.http.routers.user-service.rule=PathPrefix(`/api/v1/users`)"
- "traefik.http.routers.user-service.entrypoints=websecure"
- "traefik.http.services.user-service.loadbalancer.server.port=8080"
- "traefik.http.middlewares.user-ratelimit.ratelimit.average=100"
- "traefik.http.routers.user-service.middlewares=user-ratelimit"
Apollo Router para GraphQL
# router.yaml
supergraph:
listen: 0.0.0.0:4000
path: /
introspection: true
telemetry:
exporters:
tracing:
otlp:
endpoint: http://otel-collector:4317
Explicación
El enrutamiento de requests es el trabajo principal del gateway. Mapea paths entrantes a servicios
backend: /api/v1/users va al servicio de usuarios, y ese servicio puede moverse o escalar sin
que el cliente lo sepa.
Concerns como auth, rate limiting y caching pertenecen al borde. Resolverlos una vez allí evita duplicar la misma lógica en cada microservicio. Trabajé en equipos que saltearon esto y terminaron con validación JWT copy-pasteada across 12 servicios — una pesadilla de mantenimiento cuando el formato del token cambió.
Un gateway GraphQL puede disparar varios requests REST a microservicios y armar una sola respuesta
tipada para el cliente. Esa es la traducción de protocolos en la práctica. El cliente pide
{ user { orders { status } } } y el gateway fetcha del servicio de usuarios, después del servicio
de órdenes, y stitches la respuesta.
La terminación SSL significa que el gateway maneja TLS para que los servicios internos usen HTTP simple dentro de una red confiable. Esto saca el overhead de TLS de las llamadas internas y centraliza el manejo de certificados en el borde donde corresponde.
Lifecycle del request
- El cliente envía el request a la URL del gateway.
- El gateway valida el JWT o API key.
- El rate limiter verifica si el cliente está dentro de los límites.
- El router matchea el path y elige el backend.
- Cache check opcional — devuelve la respuesta cacheada si está disponible.
- El proxy reenvía el request al backend.
- La respuesta vuelve, transformación opcional, y se devuelve al cliente.
Los pasos 2–4 pasan en milisegundos. El gateway agrega latencia, pero ahorra round trips con caching, connection pooling y short-circuiting de requests inválidos antes de que lleguen al backend.
Variantes
| Tipo | Gestión | Ideal para | Compromiso |
|---|---|---|---|
| Self-hosted (Kong, Traefik) | Control total | On-prem, compliance | Overhead operacional |
| Managed (AWS, Azure, GCP) | Serverless | Cloud-native, escalar | Vendor lock-in, costo |
| Custom built | Flexibilidad máxima | Requisitos únicos | Costo de desarrollo |
| Service mesh (Istio ingress) | Kubernetes-native | Clusters K8s | Complejidad |
Buenas Prácticas
- Implementá circuit breakers en el gateway para dejar de enviar tráfico a backends con fallos.
- Usá versionado en el path, como
/api/v1/users, en vez de headers. Mantiene el routing explícito y las claves de caché simples. - Centralizá observability: inyectá trace IDs en el borde y propagalos downstream.
- Descargá autenticación validando JWTs o API keys en el gateway y reenviá headers de contexto de usuario a los backends.
- Cacheá endpoints read-heavy en el borde, como catálogos de productos y datos de configuración.
Errores Comunes
- Tratar al gateway como un servicio más y meter lógica de negocio. Dejá routing, auth y rate limiting en el borde; las reglas de negocio van en los servicios de dominio.
- Publicar sin timeouts ni reglas de retry. Definí timeouts por ruta y reintentá solo operaciones idempotentes.
- Correr una sola instancia del gateway. Usá al menos dos instancias detrás de un load balancer con health checks.
- Ignorar necesidades específicas de clientes. Las apps mobile suelen necesitar payloads más pequeños que las web, así que un gateway backend-for-frontend (BFF) puede valer la pena.
See Also
- Documentación de Kong Gateway — docs oficiales de Kong cubriendo plugins, routing y configuración declarativa.
- Documentación de Traefik — docs de Traefik proxy con integración de Docker, Kubernetes y Let’s Encrypt.
- Docs de Apollo Router — Apollo Router para gateways GraphQL federados, con configuración de supergraph y telemetry.
- AWS API Gateway — docs del servicio managed de API Gateway para deployments cloud-native.
- Recipe de microservices patterns — nuestra guía de los patrones core de microservicios que trabajan junto a un API gateway.
- Recipe de load balancing — nuestro deep dive sobre estrategias de load balancing para alta disponibilidad del gateway.
Preguntas frecuentes
¿Uso un API gateway o un service mesh?
Usá un gateway para tráfico north-south — clientes externos hacia el cluster. Usá un service mesh para tráfico east-west — servicios hablando entre sí dentro del cluster. Se complementan.
¿Cómo manejo GraphQL en un gateway?
Elegí un gateway GraphQL como Apollo Router o Hasura. Cada microservicio expone un subgraph y el gateway los une en un supergraph.
¿Agrega latencia un gateway?
Sí, pero generalmente solo 1–5 ms para un gateway bien afinado. Los beneficios — caching, connection pooling y auth centralizado — suelen reducir la latencia total.
¿Cómo aseguro llamadas servicio a servicio detrás de un gateway?
El gateway valida tokens externos. Para llamadas internas, usá mTLS o tokens internos firmados. Nunca confiés en headers de auth orientados al usuario para llamadas internas.
¿Un gateway puede reemplazar un load balancer?
No exactamente. Un gateway maneja routing, auth y traducción de protocolos. Un load balancer distribuye tráfico across instancias del mismo servicio. En la práctica, los gateways viven detrás de un load balancer para alta disponibilidad — el LB enruta a instancias saludables del gateway, y cada gateway enruta al backend correcto.
¿Por qué mi gateway agrega latencia incluso en cache hits?
El gateway igual tiene que parsear el request, validar auth, buscar la cache key y serializar la respuesta. Eso suele ser 1–3 ms en cache hits. Si ves más, revisá tu cache backend — un cache in-memory es más rápido que Redis para claves calientes, pero Redis gana cuando tenés dos o más instancias del gateway.
Recursos Relacionados
Diseñar Microservicios Resilientes con Circuit Breakers,
Cómo construir sistemas distribuidos tolerantes a fallos usando patrones de microservicios incluyendo circuit breakers, bulkheads, retries con backoff y sagas para gestión de transacciones.
RecipeDistribuir Tráfico con Algoritmos de Load Balancing
Cómo distribuir requests entrantes entre múltiples servidores usando round-robin, least-connections, weighted y consistent hashing con health checks y failover.
RecipeLimitacion de tasa (Rate Limiting)
Cómo implementar rate limiting en APIs usando token bucket, sliding window y fixed window en Python, JavaScript y Java.
RecipeAutenticación JWT
Cómo generar, validar y refrescar JSON Web Tokens para autenticación de APIs sin estado.
RecipeConstruir Sistemas Resilientes con el Circuit Breaker
Cómo prevenir fallas en cascada en sistemas distribuidos usando circuit breakers con estados open, closed y half-open en Java, TypeScript y Python.
RecipeAsegurar y Observar Microservicios con un Service Mesh
Cómo desplegar Istio o Linkerd para agregar mTLS, gestión de tráfico, observabilidad y enforcement de políticas a microservicios sin cambiar código de aplicación.