API Gateway Communication — Centralized Entry Point for Microservices
In this tutorial, you will learn about API Gateway Communication. We cover key concepts, practical examples, and best practices to help you master this topic.
An API gateway is a single entry point for all client requests that routes to the appropriate microservice, handles cross-cutting concerns like auth and Rate Limiting, and aggregates responses from multiple services.
What You'll Learn
By the end of this lesson you will design an API gateway architecture, implement request routing and response aggregation, configure rate limiting and authentication at the gateway level, translate between protocols, and understand gateway pros and cons.
Why It Matters
Without a gateway, clients must know about every microservice, handle authentication individually, and make multiple round trips to gather data. An API gateway abstracts the backend complexity, centralizes security, reduces client-side code, and enables backend Refactoring without client changes.
Real-World Use
DodaZIP's API gateway handles all external traffic. Mobile apps and web frontends send requests to the gateway, which routes to the appropriate internal services, adds authentication headers, enforces rate limits, and aggregates responses from file storage, user profile, and billing services.
flowchart LR
A[Client] -->|Single Entry Point| B[API Gateway]
B --> C[User Service]
B --> D[Order Service]
B --> E[Payment Service]
B --> F[File Service]
B --> G[Authentication]
B --> H[Rate Limiting]
style B fill:#2d3748,color:#fff
Gateway Responsibilities
Core functions of an API gateway.
# gateway_responsibilities.py
# API gateway responsibilities
def gateway_responsibilities():
print("API Gateway Core Responsibilities")
print("=" * 40)
print()
responsibilities = [
{
"function": "Request Routing",
"desc": "Route incoming requests to the appropriate microservice based on URL path, headers, or method.",
"example": "/api/users/* -> user-service, /api/orders/* -> order-service"
},
{
"function": "Authentication",
"desc": "Validate tokens and authenticate requests before they reach backend services.",
"example": "Validate JWT, extract user ID, attach to forwarded headers"
},
{
"function": "Rate Limiting",
"desc": "Limit requests from a client to prevent abuse and protect backend services.",
"example": "100 requests/minute per API key, 429 response when exceeded"
},
{
"function": "Response Aggregation",
"desc": "Combine responses from multiple services into a single response to reduce client round trips.",
"example": "GET /order-detail/123 -> gateway calls order + payment + shipping services"
},
{
"function": "Protocol Translation",
"desc": "Convert between external and internal protocols (REST <-> gRPC, HTTP/1.1 <-> HTTP/2).",
"example": "External REST client -> gateway translates to internal gRPC calls"
},
]
for r in responsibilities:
print(f"{r['function']:30s}")
print(f" {r['desc']}")
print(f" Example: {r['example']}")
print()
gateway_responsibilities()
Request Routing Implementation
Routing requests to the correct service.
# routing.py
# API gateway request routing
def request_routing():
print("API Gateway Request Routing")
print("=" * 40)
print()
routing_code = """
class ApiGateway:
def __init__(self, registry):
self.registry = registry
self.routes = [
Route("/api/users/", "user-service", ["GET", "POST", "PUT", "DELETE"]),
Route("/api/orders/", "order-service", ["GET", "POST"]),
Route("/api/orders/*/cancel", "order-service", ["POST"]),
Route("/api/payments/", "payment-service", ["GET", "POST"]),
Route("/api/files/", "file-service", ["GET", "POST", "DELETE"]),
]
def route_request(self, request):
# Find matching route
for route in self.routes:
if self._matches(request, route):
return self._forward(request, route)
return {"error": "route not found"}, 404
def _matches(self, request, route):
# Check path prefix and HTTP method
path_matches = request.path.startswith(route.path_pattern) or \
self._wildcard_match(request.path, route.path_pattern)
method_matches = request.method in route.methods
return path_matches and method_matches
def _forward(self, request, route):
service_name = route.service
instances = self.registry.get_instances(service_name)
if not instances:
return {"error": f"{service_name} unavailable"}, 503
# Select instance and forward
instance = self._select_instance(instances)
target_url = f"http://{instance['host']}:{instance['port']}{request.path}"
# Add gateway metadata to headers
headers = dict(request.headers)
headers["X-Forwarded-By"] = "api-gateway"
headers["X-User-Id"] = request.user_id # Set by auth middleware
return self._proxy(target_url, request.method, headers, request.body)
def _wildcard_match(self, path, pattern):
parts = path.split("/")
pat_parts = pattern.split("/")
if len(parts) != len(pat_parts):
return False
for p, pp in zip(parts, pat_parts):
if pp != "*" and p != pp:
return False
return True
"""
print(routing_code)
request_routing()
Response Aggregation
Combining multiple service responses.
# aggregation.py
# Response aggregation pattern
def response_aggregation():
print("Response Aggregation")
print("=" * 40)
print()
code = """
import asyncio
import httpx
class AggregationGateway:
"""
Aggregates responses from multiple services
to reduce client round trips.
"""
async def get_order_detail(self, order_id, user_id):
# Call multiple services in parallel
async with httpx.AsyncClient() as client:
order_task = self._get_order(client, order_id)
payment_task = self._get_payment(client, order_id)
shipping_task = self._get_shipping(client, order_id)
order, payment, shipping = await asyncio.gather(
order_task, payment_task, shipping_task,
return_exceptions=True
)
# Build aggregated response
response = {
"order": order if not isinstance(order, Exception) else None,
"payment": payment if not isinstance(payment, Exception) else None,
"shipping": shipping if not isinstance(shipping, Exception) else None,
}
# Add error info for any failed services
errors = {}
if isinstance(order, Exception):
errors["order"] = str(order)
if isinstance(payment, Exception):
errors["payment"] = str(payment)
if isinstance(shipping, Exception):
errors["shipping"] = str(shipping)
if errors:
response["_errors"] = errors
return response
async def _get_order(self, client, order_id):
resp = await client.get(
f"http://order-service/api/orders/{order_id}",
timeout=5.0
)
resp.raise_for_status()
return resp.json()
async def _get_payment(self, client, order_id):
resp = await client.get(
f"http://payment-service/api/payments?order_id={order_id}",
timeout=5.0
)
resp.raise_for_status()
return resp.json()
async def _get_shipping(self, client, order_id):
resp = await client.get(
f"http://shipping-service/api/shipping?order_id={order_id}",
timeout=5.0
)
resp.raise_for_status()
return resp.json()
# This reduces the client from 3 HTTP calls to 1
"""
print(code)
response_aggregation()
Rate Limiting at the Gateway
Protecting backend services from abuse.
# rate_limiting.py
# Rate limiting implementation
def rate_limiting():
print("API Gateway Rate Limiting")
print("=" * 40)
print()
code = """
import time
import redis
class RateLimiter:
"""Sliding window rate limiter."""
def __init__(self, redis_client):
self.redis = redis_client
def check_rate_limit(self, client_id, limit, window_seconds=60):
"""
Check if client_id has exceeded the rate limit.
Returns (allowed, remaining, reset_time).
"""
key = f"ratelimit:{client_id}"
now = time.time()
window_start = now - window_seconds
# Remove old entries
self.redis.zremrangebyscore(key, 0, window_start)
# Count requests in current window
request_count = self.redis.zcard(key)
if request_count >= limit:
# Get oldest request time for reset header
oldest = self.redis.zrange(key, 0, 0, withscores=True)
reset_time = oldest[0][1] + window_seconds if oldest else now
return False, 0, int(reset_time)
# Add current request
self.redis.zadd(key, {str(now): now})
self.redis.expire(key, window_seconds)
remaining = limit - request_count - 1
return True, remaining, int(now + window_seconds)
def middleware(self, request, limit=100, window=60):
client_id = self._get_client_id(request)
allowed, remaining, reset = self.check_rate_limit(
client_id, limit, window
)
if not allowed:
return {
"error": "rate_limit_exceeded",
"message": f"Limit of {limit} requests per {window}s exceeded",
"retry_after": reset - time.time()
}, 429
# Add rate limit headers
request.response_headers["X-RateLimit-Remaining"] = str(remaining)
request.response_headers["X-RateLimit-Reset"] = str(reset)
return None # Continue processing
def _get_client_id(self, request):
# Use API key, user ID, or IP address
return request.headers.get("X-API-Key") or \
request.headers.get("X-User-Id") or \
request.client.host
"""
print(code)
rate_limiting()
Common Mistakes
Gateway becoming a monolith: Adding too much business logic to the gateway recreates the monolith. Keep the gateway focused on cross-cutting concerns only.
Single point of failure: The gateway is critical infrastructure. Deploy multiple instances behind a load balancer and use health checks for auto-recovery.
Request overhead from aggregation: Aggregating many service calls per request increases gateway latency. Use async parallel calls and set appropriate timeouts.
Not handling partial failures: When aggregating, one service may fail while others succeed. Return partial data with error indicators instead of failing the entire request.
Ignoring Websocket and streaming: REST APIs are not the only protocol. Gateways must also support WebSocket upgrades and gRPC streaming for real-time features.
Practice Questions
What is the primary purpose of an API gateway? To serve as a single entry point for client requests, routing to the appropriate microservice and handling cross-cutting concerns.
How does response aggregation improve client performance? It reduces multiple round trips into a single request, with the gateway making parallel calls to backend services.
What is the difference between an API gateway and a load balancer? A load balancer distributes traffic among instances of the same service. A gateway routes to different services and handles cross-cutting concerns.
Why should the gateway not contain business logic? Business logic in the gateway makes it a monolith that is hard to maintain, test, and deploy independently from services.
Challenge: Design an API gateway for a video streaming platform. Define routes for authentication, video catalog, user profile, recommendations, and watch history. Include rate limiting (different limits for free vs premium users), response aggregation for the home page, and WebSocket support for live chat.
FAQ
Mini Project
Design an API gateway for a ride-sharing application with services: user, driver, ride, payment, and notification. Define routes, implement authentication middleware (validate JWT), rate limiting (50 requests/minute), and a response aggregation endpoint that returns ride details with driver info and payment status.
def rideshare_gateway():
print("Ride-Sharing API Gateway Design")
print("=" * 45)
print()
print("Routes:")
print(" POST /api/auth/login -> user-service")
print(" GET /api/users/profile -> user-service")
print(" POST /api/rides/request -> ride-service")
print(" GET /api/rides/{id} -> ride-service")
print(" GET /api/rides/{id}/detail -> AGGREGATED")
print(" POST /api/payments/methods -> payment-service")
print(" GET /api/drivers/nearby -> driver-service")
print()
print("Aggregated Endpoint: GET /api/rides/{id}/detail")
print(" Calls: ride-service (ride details)")
print(" driver-service (driver name, rating, car)")
print(" payment-service (fare breakdown)")
print(" Returns: Combined JSON in single response")
print()
print("Rate Limiting:")
print(" Free users: 20 requests/min")
print(" Premium: 100 requests/min")
print(" Drivers: 200 requests/min")
rideshare_gateway()
What's Next
Next: Circuit Breaker for resilient service communication.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro