Skip to content
LowLevelDesign Mastery

API Gateway Pattern

One door to rule them all

When you have multiple microservices, clients need to know about each one:

Client without a gateway calling user, order, payment and product microservices directly on separate localhost ports

Problems:

  • Clients need to know all services - Mobile apps must hardcode multiple service URLs, making updates difficult
  • Multiple round trips - Fetching related data requires multiple requests, increasing latency
  • Cross-cutting concerns duplicated (auth, rate limiting) - Each service implements its own authentication and rate limiting logic
  • Hard to change service locations - Moving services requires updating all clients

Solution: API Gateway - A single entry point that routes requests, handles cross-cutting concerns, and aggregates responses.


API Gateway is a single entry point that sits between clients and microservices. It acts as a reverse proxy that routes client requests to appropriate backend services, handles cross-cutting concerns like authentication and rate limiting, and can aggregate responses from multiple services.

Instead of clients directly calling multiple microservices, they make requests to a single API Gateway. The gateway handles routing, security, monitoring, and other cross-cutting concerns, allowing backend services to focus on business logic. This pattern simplifies client code, centralizes security, and provides a unified interface to your microservices architecture.

Client sending a single request to an API gateway that routes to user, order and payment services and returns an aggregated response
  1. Request Routing - Route to correct service
  2. Authentication - Verify who you are
  3. Authorization - Check permissions
  4. Rate Limiting - Control request volume
  5. Load Balancing - Distribute load
  6. Request Aggregation - Combine multiple calls
  7. Protocol Translation - REST to gRPC, etc.
  8. Monitoring - Logging, metrics

API gateway core functions: request routing, authentication, rate limiting and response aggregation

Route requests to appropriate services:

API gateway routing GET /api/users/123 to the user service based on path rules for users, orders and products

Routing Examples:

RequestRoute To
GET /api/users/123User Service
POST /api/ordersOrder Service
GET /api/productsProduct Service
POST /api/paymentsPayment Service

Centralized security:

API gateway validating a bearer token with an auth service, then forwarding an authenticated request with user context to the backend

Benefits:

  • Services don’t need auth logic - Backend services can focus on business logic without implementing authentication
  • Consistent security - All requests go through the same security checks, ensuring uniform enforcement
  • Token validation once - Validate tokens at the gateway instead of in every service, improving performance

Control request volume per client:

API gateway rate limiter tracking requests from two clients and forwarding only allowed requests to protect the backend service

Combine multiple service calls:

API gateway aggregating a user dashboard request by calling user, order and product services in parallel and combining results

Without Gateway (Multiple Requests):

Client → User Service (get user)
Client → Order Service (get orders)
Client → Product Service (get products)
= 3 round trips

With Gateway (Aggregation):

Client → Gateway → [User, Order, Product Services] → Gateway → Client
= 1 round trip

API gateway request pipeline that validates, authenticates, rate limits and routes GET /api/users/123 to the user service
💡 Tip: Click dropdown to switch between languages
"api_gateway.py
from flask import Flask, request, jsonify
import requests
from functools import wraps
from typing import Dict, Optional
app = Flask(__name__)
# Service registry
SERVICES = {
'users': 'http://user-service:8001',
'orders': 'http://order-service:8002',
'products': 'http://product-service:8003',
}
# Rate limiting (simplified)
request_counts = {}
def rate_limit(max_requests=100, window=60):
"""Rate limiting decorator"""
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
client_id = request.remote_addr
# Reset counter if window expired
if client_id not in request_counts:
request_counts[client_id] = {'count': 0, 'reset_at': time.time() + window}
# Check limit
if request_counts[client_id]['count'] >= max_requests:
return jsonify({'error': 'Rate limit exceeded'}), 429
# Increment counter
request_counts[client_id]['count'] += 1
return f(*args, **kwargs)
return wrapper
return decorator
def authenticate():
"""Simple authentication"""
token = request.headers.get('Authorization')
if not token or not token.startswith('Bearer '):
return None
# Validate token (simplified)
token_value = token.replace('Bearer ', '')
# In production, validate with auth service
return {'user_id': '123', 'role': 'user'}
def route_to_service(service_name: str, path: str):
"""Route request to appropriate service"""
service_url = SERVICES.get(service_name)
if not service_url:
return None, 404
# Forward request
url = f"{service_url}{path}"
method = request.method
headers = dict(request.headers)
# Remove host header (service will set its own)
headers.pop('Host', None)
response = requests.request(
method=method,
url=url,
headers=headers,
data=request.get_data(),
params=request.args
)
return response.json(), response.status_code
@app.route('/api/users/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
@rate_limit(max_requests=100)
def users_proxy(path):
"""Proxy requests to user service"""
user = authenticate()
if not user:
return jsonify({'error': 'Unauthorized'}), 401
return route_to_service('users', f'/users/{path}')
@app.route('/api/orders/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
@rate_limit(max_requests=50)
def orders_proxy(path):
"""Proxy requests to order service"""
user = authenticate()
if not user:
return jsonify({'error': 'Unauthorized'}), 401
return route_to_service('orders', f'/orders/{path}')
@app.route('/api/user-dashboard/<user_id>')
@rate_limit(max_requests=20)
def user_dashboard(user_id):
"""Aggregate multiple service calls"""
user = authenticate()
if not user:
return jsonify({'error': 'Unauthorized'}), 401
# Call multiple services in parallel
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
user_future = executor.submit(
requests.get,
f"{SERVICES['users']}/users/{user_id}"
)
orders_future = executor.submit(
requests.get,
f"{SERVICES['orders']}/orders?userId={user_id}"
)
user_data = user_future.result().json()
orders_data = orders_future.result().json()
# Aggregate results
return jsonify({
'user': user_data,
'orders': orders_data
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)

API gateway patterns: simple proxy forwarding requests as-is, aggregator combining service calls, and router matching path or method

Just route requests:

Client → Gateway → Service

Use when:

  • Simple routing needed
  • No aggregation
  • Services handle their own concerns

Gateway tailored for specific client:

Backend for frontend pattern with mobile and web apps each calling their own optimized gateway in front of shared backend services

Aggregates multiple service calls:

Client → Gateway → [Service1, Service2, Service3] → Gateway → Client

Use when:

  • Need to reduce round trips
  • Client needs data from multiple services
  • Services are independent

API gateway trade-off weighing benefits like a single entry point and centralized auth against single point of failure and complexity
  • Single Entry Point - Clients only know gateway, simplifying client code and deployment
  • Centralized Concerns - Auth, rate limiting in one place, reducing duplication and ensuring consistency
  • Hides Complexity - Services can change without affecting clients, enabling independent deployment
  • Request Aggregation - Reduce round trips by combining multiple service calls into one request
  • Protocol Translation - REST to gRPC, etc., allowing services to use different protocols
  • Easier Monitoring - Centralized logging/metrics provide unified observability
  • Single Point of Failure - Mitigate with multiple instances and load balancing
  • Potential Bottleneck - Can become overloaded if not properly scaled, requiring careful capacity planning
  • Additional Latency - Extra hop adds network latency, though usually minimal
  • More Complexity - Another component to manage, deploy, and monitor
  • Can Become Monolithic - If too much logic added, gateway can become a bottleneck and violate microservices principles

Popular API gateway solutions: Kong as open-source plugin-based, AWS API Gateway as managed serverless, and Nginx as reverse proxy
SolutionTypeBest For
KongOpen sourceGeneral purpose, plugin ecosystem
AWS API GatewayManagedAWS ecosystem
ZuulOpen sourceNetflix stack, Spring Cloud
EnvoyOpen sourceService mesh, high performance
NGINXOpen sourceSimple routing, high performance

API gateway internals with a middleware chain for auth, rate limiting and validation, then a router and handler calling the backend

At the code level, gateways translate to routing logic, middleware, and aggregation patterns.

💡 Tip: Click dropdown to switch between languages
"gateway_design.py
from abc import ABC, abstractmethod
from typing import Dict, Any, List
class Gateway(ABC):
"""Base gateway interface"""
@abstractmethod
def route(self, request: Dict[str, Any]) -> Dict[str, Any]:
"""Route request to appropriate service"""
pass
@abstractmethod
def authenticate(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Authenticate request"""
pass
class APIGateway(Gateway):
def __init__(self):
self.routes = {}
self.middleware = []
def add_route(self, path: str, service: str):
"""Add routing rule"""
self.routes[path] = service
def add_middleware(self, middleware):
"""Add middleware (auth, rate limiting, etc.)"""
self.middleware.append(middleware)
def route(self, request: Dict[str, Any]) -> Dict[str, Any]:
"""Route with middleware"""
# Apply middleware
for mw in self.middleware:
request = mw.process(request)
if request.get('error'):
return request
# Route to service
path = request['path']
service = self.find_service(path)
if not service:
return {'error': 'Service not found', 'status': 404}
# Forward to service
return self.forward_to_service(service, request)
def aggregate(self, requests: List[Dict[str, Any]]) -> Dict[str, Any]:
"""Aggregate multiple service calls"""
results = {}
for req in requests:
service = self.find_service(req['path'])
result = self.forward_to_service(service, req)
results[req['path']] = result
return results

Single Entry Point

API Gateway provides one door for all clients. Hides microservices complexity.

Request Aggregation

Combine multiple service calls into one request. Reduces round trips significantly.

Centralized Security

Handle authentication, authorization, and rate limiting in one place. Services stay simple.

Load Balancing

Gateway can distribute load across service instances. Services don’t need to know about each other.