API Gateway: Routing, Auth, Rate Limiting
Dominá API gateway architecture: request routing, authentication, rate limiting, request shaping, response caching, protocol translation y patrones de producción.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Introducción
Un API gateway se sienta entre clients y backend services. Handlea cross-cutting concerns: routing, authentication, rate limiting, request shaping, response caching y protocol translation. Backend services se focusan en business logic en vez de infrastructure. A continuación: routing, auth, rate limiting, request/response transformation, caching, protocol translation y production deployment patterns.
Core Responsibilities
Client
│
▼
┌─────────────────────────────────┐
│ API Gateway │
│ │
│ ┌─────────┐ ┌──────────────┐ │
│ │ Routing │ │ Authentication│ │
│ └─────────┘ └──────────────┘ │
│ ┌─────────┐ ┌──────────────┐ │
│ │ Rate │ │ Request │ │
│ │ Limiting│ │ Shaping │ │
│ └─────────┘ └──────────────┘ │
│ ┌─────────┐ ┌──────────────┐ │
│ │ Caching │ │ Protocol │ │
│ │ │ │ Translation │ │
│ └─────────┘ └──────────────┘ │
│ ┌─────────┐ ┌──────────────┐ │
│ │ Logging │ │ Load │ │
│ │ Metrics │ │ Balancing │ │
│ └─────────┘ └──────────────┘ │
└─────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│Users │ │Orders│ │Billing│
│Svc │ │Svc │ │Svc │
└──────┘ └──────┘ └──────┘
Request Routing
Path-based routing
# Kong declarative config
_format_version: "3.0"
services:
- name: user-service
url: http://user-service:3000
routes:
- name: users
paths:
- /api/users
strip_path: true
methods: [GET, POST, PUT, DELETE]
- name: order-service
url: http://order-service:3000
routes:
- name: orders
paths:
- /api/orders
strip_path: true
methods: [GET, POST, PUT, DELETE]
- name: billing-service
url: http://billing-service:3000
routes:
- name: billing
paths:
- /api/billing
- /api/invoices
strip_path: true
NGINX-based routing
# nginx.conf
upstream user_service {
server user-service:3000;
}
upstream order_service {
server order-service:3000;
}
server {
listen 80;
location /api/users/ {
proxy_pass http://user_service;
proxy_set_header Host $host;
proxy_set_header X-Request-ID $request_id;
}
location /api/orders/ {
proxy_pass http://order_service;
proxy_set_header Host $host;
proxy_set_header X-Request-ID $request_id;
}
# Default — 404 para unmatched routes
location / {
return 404;
}
}
Authentication
JWT validation en gateway
// gateway/middleware/auth.ts — Validá JWT antes de llegar al backend
import jwt from 'jsonwebtoken';
interface AuthConfig {
jwtSecret: string;
publicPaths: string[]; // Paths que no requieren auth
}
function authMiddleware(config: AuthConfig) {
return (req: Request, res: Response, next: NextFunction) => {
// Skipeá auth para public paths
if (config.publicPaths.some(path => req.path.startsWith(path))) {
return next();
}
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Missing authorization header' });
}
try {
const payload = jwt.verify(token, config.jwtSecret) as JwtPayload;
// Inyectá user context en headers para backend services
req.headers['x-user-id'] = payload.sub;
req.headers['x-user-roles'] = payload.roles.join(',');
req.headers['x-user-scopes'] = payload.scopes.join(',');
next();
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(401).json({ error: 'Invalid token' });
}
};
}
API key authentication
// gateway/middleware/apikey.ts — API key validation con rate limits per key
async function apiKeyMiddleware(req: Request, res: Response, next: NextFunction) {
const apiKey = req.headers['x-api-key'];
if (!apiKey) {
return res.status(401).json({ error: 'Missing API key' });
}
const keyData = await keyStore.get(apiKey);
if (!keyData) {
return res.status(401).json({ error: 'Invalid API key' });
}
if (keyData.expiresAt < new Date()) {
return res.status(401).json({ error: 'API key expired' });
}
// Checkeá rate limit para este key
const allowed = await rateLimiter.check(`apikey:${keyData.id}`, keyData.rateLimit);
if (!allowed) {
return res.status(429).json({ error: 'Rate limit exceeded' });
}
// Inyectá key context
req.headers['x-api-key-id'] = keyData.id;
req.headers['x-tenant-id'] = keyData.tenantId;
next();
}
Rate Limiting
Token bucket rate limiter
// gateway/middleware/rateLimit.ts — Token bucket implementation
class TokenBucketRateLimiter {
private buckets = new Map<string, { tokens: number; lastRefill: number }>();
constructor(
private readonly capacity: number,
private readonly refillRate: number, // tokens per second
) {}
async check(key: string): Promise<{ allowed: boolean; remaining: number }> {
const now = Date.now();
let bucket = this.buckets.get(key);
if (!bucket) {
bucket = { tokens: this.capacity, lastRefill: now };
this.buckets.set(key, bucket);
}
// Refilleá tokens basado en elapsed time
const elapsed = (now - bucket.lastRefill) / 1000;
bucket.tokens = Math.min(this.capacity, bucket.tokens + elapsed * this.refillRate);
bucket.lastRefill = now;
if (bucket.tokens >= 1) {
bucket.tokens -= 1;
return { allowed: true, remaining: Math.floor(bucket.tokens) };
}
return { allowed: false, remaining: 0 };
}
}
// Middleware con per-tier limits
const rateLimitTiers = {
free: { capacity: 10, refillRate: 0.1 }, // 10 requests, refill 1 per 10s
basic: { capacity: 100, refillRate: 1 }, // 100 requests, refill 1 per second
premium: { capacity: 1000, refillRate: 10 }, // 1000 requests, refill 10 per second
};
function rateLimitMiddleware(limiter: TokenBucketRateLimiter) {
return async (req: Request, res: Response, next: NextFunction) => {
const tier = req.headers['x-tier'] as string || 'free';
const config = rateLimitTiers[tier] || rateLimitTiers.free;
const key = `${req.headers['x-user-id']}:${req.path}`;
const result = await limiter.check(key);
res.setHeader('X-RateLimit-Remaining', result.remaining);
res.setHeader('X-RateLimit-Limit', config.capacity);
if (!result.allowed) {
res.setHeader('Retry-After', Math.ceil(1 / config.refillRate));
return res.status(429).json({ error: 'Rate limit exceeded' });
}
next();
};
}
Request Shaping
Request/response transformation
// gateway/middleware/transform.ts — Transformá requests y responses
function requestTransformMiddleware(req: Request, res: Response, next: NextFunction) {
// Agregá request ID para tracing
req.headers['x-request-id'] = crypto.randomUUID();
// Agregá timestamp
req.headers['x-request-timestamp'] = new Date().toISOString();
// Version routing: /api/v1/users → /api/users con version header
const versionMatch = req.path.match(/^\/api\/v(\d+)\//);
if (versionMatch) {
req.headers['x-api-version'] = versionMatch[1];
req.url = req.url.replace(`/v${versionMatch[1]}`, '');
}
next();
}
function responseTransformMiddleware(req: Request, res: Response, next: NextFunction) {
const originalSend = res.send;
res.send = function(data: any) {
// Wrapeá response en standard envelope
if (typeof data === 'string') {
try {
const parsed = JSON.parse(data);
const enveloped = {
data: parsed,
meta: {
requestId: req.headers['x-request-id'],
timestamp: new Date().toISOString(),
version: req.headers['x-api-version'] || '1',
},
};
return originalSend.call(this, JSON.stringify(enveloped));
} catch {
// Non-JSON response, pass through
}
}
return originalSend.call(this, data);
};
next();
}
Response Caching
// gateway/middleware/cache.ts — Cacheá GET responses
class ResponseCache {
private cache = new Map<string, { body: string; status: number; expiresAt: number }>();
async get(key: string): Promise<CachedResponse | null> {
const entry = this.cache.get(key);
if (!entry) return null;
if (entry.expiresAt < Date.now()) {
this.cache.delete(key);
return null;
}
return { body: entry.body, status: entry.status };
}
set(key: string, body: string, status: number, ttlSeconds: number): void {
this.cache.set(key, {
body,
status,
expiresAt: Date.now() + ttlSeconds * 1000,
});
}
}
function cacheMiddleware(cache: ResponseCache, defaultTtl: number = 60) {
return async (req: Request, res: Response, next: NextFunction) => {
// Solo cacheá GET requests
if (req.method !== 'GET') return next();
const cacheKey = `${req.headers['x-user-id']}:${req.path}:${JSON.stringify(req.query)}`;
const cached = await cache.get(cacheKey);
if (cached) {
res.setHeader('X-Cache', 'HIT');
return res.status(cached.status).send(cached.body);
}
// Interceptá response para cachearlo
const originalSend = res.send;
res.send = function(data: any) {
if (res.statusCode === 200) {
const ttl = parseInt(res.getHeader('Cache-Control')?.toString().match(/max-age=(\d+)/)?.[1] || '') || defaultTtl;
cache.set(cacheKey, data.toString(), res.statusCode, ttl);
res.setHeader('X-Cache', 'MISS');
}
return originalSend.call(this, data);
};
next();
};
}
Protocol Translation
// gateway/protocol/grpc-to-rest.ts — REST to gRPC translation
import { GrpcClient } from '@grpc/grpc-js';
class GrpcTranslationMiddleware {
private clients = new Map<string, GrpcClient>();
constructor(private serviceRegistry: ServiceRegistry) {}
async handle(req: Request, res: Response): Promise<void> {
const service = this.serviceRegistry.resolve(req.path);
const client = this.getClient(service);
// Mapeá REST a gRPC method
const grpcMethod = this.mapRestToGrpc(req.method, req.path, service);
const grpcRequest = this.transformRequest(req);
try {
const grpcResponse = await client.call(grpcMethod, grpcRequest);
const restResponse = this.transformResponse(grpcResponse);
res.json(restResponse);
} catch (error) {
this.translateGrpcError(error, res);
}
}
private mapRestToGrpc(method: string, path: string, service: GrpcService): string {
// GET /api/users/123 → UserService.GetUser
// POST /api/users → UserService.CreateUser
const mapping = {
'GET:/api/users/:id': 'GetUser',
'GET:/api/users': 'ListUsers',
'POST:/api/users': 'CreateUser',
'PUT:/api/users/:id': 'UpdateUser',
'DELETE:/api/users/:id': 'DeleteUser',
};
return mapping[`${method}:${service.pattern}`] || 'Unknown';
}
}
Production Deployment
Kubernetes deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
spec:
replicas: 3
template:
spec:
containers:
- name: gateway
image: registry.io/api-gateway:latest
ports:
- containerPort: 8080
env:
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: gateway-secrets
key: redis-url
resources:
limits:
cpu: 1000m
memory: 512Mi
requests:
cpu: 500m
memory: 256Mi
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: api-gateway
spec:
type: LoadBalancer
ports:
- port: 443
targetPort: 8080
selector:
app: api-gateway
Best Practices
-
For a deeper guide, see API Gateway Design: Resilience, Routing, and Security.
-
Mantené el gateway stateless — usá Redis para rate limiting y caching, no in-memory
-
Usá circuit breakers para backend calls — preventí cascading failures
-
Seteá per-service timeouts — different services tienen different latency profiles
-
Usá request IDs para tracing — inyectá en el gateway, propagá a all services
-
Validá input en el gateway — rejecteá malformed requests antes de que lleguen a services
-
Usá separate gateways para internal y external traffic — different auth y rate limits
-
Monitoreá gateway metrics — request rate, error rate, latency, cache hit rate
-
Versioná tu API en el gateway — routeéa
/v1/y/v2/a different service versions -
Usá connection pooling a backends — reducí TCP handshake overhead
-
Implementá graceful shutdown — draineá connections antes de terminating
Common Mistakes
- Business logic en el gateway: el gateway debería handlear cross-cutting concerns only. Business rules belong en services.
- No circuit breaker: un slow service blockea gateway threads, affecting all services. Usá circuit breakers con timeouts.
- In-memory rate limiting en multi-instance deployments: cada instance tiene su own counter. Usá Redis o un shared store.
- No request size limits: large payloads pueden exhaust memory. Seteá max body size.
- Single point of failure: deployeá multiple gateway instances detrás de un load balancer.
- No API versioning: breaking changes affect all clients. Versioná APIs en el gateway level.
FAQ
¿Qué es un API gateway?
Un server que se sienta entre clients y backend services. Handlea cross-cutting concerns como routing, authentication, rate limiting, caching y protocol translation. Backend services se focusan en business logic. Ejemplos: Kong, NGINX, AWS API Gateway, Envoy.
¿Necesito un API gateway para microservices?
No siempre. Small systems (2-3 services) pueden usar direct client-to-service calls. A medida que el number de services crece, un gateway reduce duplication de auth, rate limiting y routing logic across services.
¿Cuál es la diferencia entre un API gateway y un load balancer?
Un load balancer distribute traffic across instances del same service. Un API gateway rutéa a different services, handlea auth, rate limiting y transformation. Un gateway a menudo usa un load balancer internamente para cada backend service.
¿Debería el gateway handlear authorization?
El gateway handlea authentication (quién es el user). Authorization (qué puede hacer) es típicamente handled por backend services, ya que ellos poseen business rules. El gateway puede hacer coarse-grained authorization (admin vs user) basado en roles.
¿Cómo handleo WebSocket connections a través de un API gateway?
La mayoría de gateways soportan WebSocket proxying. Configurá el gateway para upgradear HTTP a WebSocket y mantener una persistent connection. Kong, NGINX y Envoy todos soportan WebSocket proxying con connection draining.
Recursos Relacionados
Modular Monolith: Module Boundaries, Shared Kernel
Master modular monolith architecture: module boundaries, shared kernel, dependency rules, communication patterns, and incremental migration to microservices.
GuideStrangler Fig Migration: Incremental Legacy Replacement
Master the strangler fig pattern for incremental legacy migration: routing layer, feature flags, data synchronization, rollback strategies, and production patterns.
GuideAPI Gateway: Routing, Auth, Rate Limiting
Master API gateway architecture: request routing, authentication, rate limiting, request shaping, response caching, protocol translation, and production deployment patterns.
GuideEvent Sourcing and CQRS: Event Store, Projections
Master event sourcing and CQRS: event store design, aggregate roots, projections, read models, snapshots, sagas, and production patterns for event-driven systems.