API Gateway & Service Mesh
Learn how API gateways centralize cross-cutting concerns like authentication, rate limiting, and routing. Explore service mesh patterns for microservices. Master the BFF pattern, API composition, and request transformation.
Why API Gateways Matter
As your API ecosystem grows, you face recurring challenges: authentication for every service, rate limiting to prevent abuse, logging for observability, SSL termination, CORS handling, request transformation, and more. An API Gateway centralizes these cross-cutting concerns in a single entry point, so your backend services focus on business logic.
- Every service implements auth, rate limiting, logging
- Duplicate code across microservices
- Inconsistent security policies
- Clients must know about every backend service
- Hard to enforce standards
- Centralized authentication & authorization
- Single point for rate limiting, caching, logging
- Backend services are simpler (no cross-cutting concerns)
- Clients use one endpoint, gateway routes internally
- Easy to enforce API standards and versioning
Service Mesh takes this further for microservices architectures. Instead of a single gateway, a service mesh deploys sidecar proxies alongside each service, providing service-to-service encryption, observability, traffic management, and resilience patterns (retries, circuit breakers) without changing application code.
AWS API Gateway
AWS API Gateway is a fully managed service that acts as the front door to your APIs. It handles REST APIs, HTTP APIs (cheaper, simpler), and WebSocket APIs. Features include request validation, transformation, Lambda integration, caching, throttling, and API keys.
API Gateway Types
Full-featured, supports request/response transformation, API keys, usage plans, caching, custom authorizers.
- Use for: Complex APIs with transformation needs
- Cost: $3.50 per million requests
Simpler, cheaper, faster. Designed for low-latency proxying to Lambda, HTTP backends. OIDC/JWT auth built-in.
- Use for: Simple proxying, serverless backends
- Cost: $1.00 per million requests (71% cheaper!)
Persistent connections for real-time, two-way communication (chat apps, live dashboards, gaming).
- Use for: Real-time bidirectional communication
- Cost: $1.00 per million messages
HTTP API with Lambda Integration (SAM)
# template.yaml - API Gateway HTTP API with Lambda
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
# HTTP API (cheaper than REST API)
ApiGateway:
Type: AWS::Serverless::HttpApi
Properties:
CorsConfiguration:
AllowOrigins:
- "*"
AllowMethods:
- GET
- POST
- PUT
- DELETE
AllowHeaders:
- "*"
Auth:
Authorizers:
JwtAuthorizer:
IdentitySource: $request.header.Authorization
JwtConfiguration:
Audience:
- your-api-audience
Issuer: https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxx
# Lambda function
ApiFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./
Handler: app.main.handler
Runtime: python3.11
Events:
GetUsers:
Type: HttpApi
Properties:
ApiId: !Ref ApiGateway
Path: /users
Method: GET
CreateUser:
Type: HttpApi
Properties:
ApiId: !Ref ApiGateway
Path: /users
Method: POST
Auth:
Authorizer: JwtAuthorizer # Require JWT for POST
Outputs:
ApiUrl:
Description: API Gateway URL
Value: !Sub "https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/"HTTP API is 71% cheaper and lower latency than REST API. Use it for simple proxying to Lambda or HTTP backends. JWT authorization is built-in (no custom authorizer needed).
Request & Response Transformation (REST API)
REST API supports VTL (Velocity Template Language) for transforming requests and responses. Map query parameters to JSON, add headers, modify response structure.
# Request mapping template - Transform query params to JSON body
{
"user_id": "$input.params('userId')",
"filter": "$input.params('filter')",
"limit": #if($input.params('limit')) $input.params('limit') #else 10 #end
}
# Response mapping template - Extract nested data
#set($data = $input.path('$.data'))
{
"items": [
#foreach($item in $data.items)
{
"id": "$item.id",
"name": "$item.name",
"created": "$item.timestamp"
}#if($foreach.hasNext),#end
#end
],
"total": $data.total
}Throttling, Caching & Usage Plans
Limit requests per second to protect backends. Configure burst and steady-state limits per stage or method.
Rate: 10,000 req/sec
Burst: 5,000 concurrentCache responses at the gateway layer. Reduce backend load and latency. TTL from 0 to 3600 seconds.
Cache size: 0.5 GB - 237 GB
Cost: ~$0.02/hour (0.5 GB)# Define usage tiers
UsagePlan:
Type: AWS::ApiGateway::UsagePlan
Properties:
UsagePlanName: BasicTier
Throttle:
RateLimit: 100 # 100 requests/second
BurstLimit: 200 # 200 concurrent
Quota:
Limit: 10000 # 10k requests per month
Period: MONTH
ApiStages:
- ApiId: !Ref RestApi
Stage: prodKong API Gateway
Kong is an open-source, cloud-native API gateway built on NGINX. It's highly performant, extensible via plugins, and can run anywhere (Kubernetes, VMs, containers). Kong provides authentication, rate limiting, logging, transformation, and 50+ plugins. Kong Enterprise adds RBAC, developer portal, and analytics.
Kong Architecture
Makes requests to Kong
Applies plugins, routes requests
Your backend API
- Service: Upstream API (e.g., users-service at users.internal:8000)
- Route: Maps incoming requests to services (e.g., /api/users → users-service)
- Plugin: Adds functionality (auth, rate limiting, logging, transformation)
- Consumer: API user who can have credentials and quotas
Running Kong with Docker Compose
# docker-compose.yml - Kong + PostgreSQL
services:
kong-database:
image: postgres:15
environment:
POSTGRES_USER: kong
POSTGRES_DB: kong
POSTGRES_PASSWORD: kong
volumes:
- kong-data:/var/lib/postgresql/data
kong-migration:
image: kong:3.8
command: kong migrations bootstrap
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
KONG_PG_PASSWORD: kong
depends_on:
- kong-database
kong:
image: kong:3.8
environment:
KONG_DATABASE: postgres
KONG_PG_HOST: kong-database
KONG_PG_PASSWORD: kong
KONG_PROXY_ACCESS_LOG: /dev/stdout
KONG_ADMIN_ACCESS_LOG: /dev/stdout
KONG_PROXY_ERROR_LOG: /dev/stderr
KONG_ADMIN_ERROR_LOG: /dev/stderr
KONG_ADMIN_LISTEN: 0.0.0.0:8001
ports:
- "8000:8000" # Proxy (HTTP)
- "8443:8443" # Proxy (HTTPS)
- "8001:8001" # Admin API
depends_on:
- kong-database
- kong-migration
volumes:
kong-data:
# Start: docker-compose up -d
# Admin API: http://localhost:8001
# Proxy: http://localhost:8000Configuring Services, Routes & Plugins
# 1. Create a Service (upstream backend) curl -i -X POST http://localhost:8001/services \ --data name=users-api \ --data url='http://users-service:8000' # 2. Create a Route (how clients access it) curl -i -X POST http://localhost:8001/services/users-api/routes \ --data 'paths[]=/users' \ --data 'methods[]=GET' \ --data 'methods[]=POST' # 3. Add Rate Limiting plugin curl -i -X POST http://localhost:8001/services/users-api/plugins \ --data name=rate-limiting \ --data config.minute=100 \ --data config.policy=local # 4. Add JWT Authentication curl -i -X POST http://localhost:8001/services/users-api/plugins \ --data name=jwt # 5. Add Request Transformer (add header) curl -i -X POST http://localhost:8001/services/users-api/plugins \ --data name=request-transformer \ --data config.add.headers=X-Service:users-api # Test the route curl http://localhost:8000/users # Kong routes this to http://users-service:8000/users # Applies rate limiting, JWT validation, header transformation
Kong's Admin API is RESTful, making it easy to configure programmatically. For production, use declarative config (YAML) or Kong's Kubernetes Ingress Controller.
Popular Kong Plugins
| Plugin | Purpose | Use Case |
|---|---|---|
| jwt | JWT authentication | Verify JWT tokens, extract claims |
| key-auth | API key authentication | Simple auth via API keys in headers |
| rate-limiting | Rate limiting | 100 requests/minute per consumer |
| cors | CORS handling | Allow cross-origin requests from browsers |
| request-transformer | Transform requests | Add/remove headers, query params, body |
| response-transformer | Transform responses | Modify response headers, body |
| prometheus | Metrics export | Expose metrics for Prometheus scraping |
| http-log | Logging | Send request/response logs to HTTP endpoint |
| proxy-cache | Response caching | Cache GET responses, reduce backend load |
| oauth2 | OAuth 2.0 provider | Kong acts as OAuth 2.0 server |
Apigee (Google Cloud)
Apigee is Google Cloud's enterprise API management platform. It's designed for large organizations managing hundreds of APIs across multiple teams. Apigee provides developer portals, monetization, analytics, quotas, and API lifecycle management. It's more than a gateway, it's a full API platform.
Enterprise Features
Self-service portal for external developers. API documentation, API keys, usage analytics, forums. Onboard partners and third-party developers easily.
Charge for API usage. Define rate plans, tiered pricing, usage quotas. Generate invoices, track revenue. Perfect for API-as-a-product businesses.
Pre-built dashboards for traffic, errors, latency, top consumers. Custom reports, anomaly detection. Export to BigQuery for deep analysis.
OAuth 2.0, OpenID Connect, mTLS, threat protection (SQL injection, XML bombs), bot detection, IP whitelisting. SOC 2, HIPAA, PCI DSS compliant.
API Proxy Configuration (XML)
Apigee uses XML-based policies for transformations, security, traffic management. Policies are chained together in flows (request, response, error).
<!-- Spike Arrest Policy - Smooth traffic spikes -->
<SpikeArrest name="SpikeArrest">
<Rate>100ps</Rate> <!-- 100 per second, smoothed -->
</SpikeArrest>
<!-- Verify API Key Policy -->
<VerifyAPIKey name="VerifyAPIKey">
<APIKey ref="request.queryparam.apikey"/>
</VerifyAPIKey>
<!-- Quota Policy - Monthly limit -->
<Quota name="QuotaPolicy">
<Interval>1</Interval>
<TimeUnit>month</TimeUnit>
<Allow count="10000"/> <!-- 10k requests per month -->
</Quota>
<!-- Assign Message Policy - Add header -->
<AssignMessage name="AddCorsHeaders">
<Set>
<Headers>
<Header name="Access-Control-Allow-Origin">*</Header>
<Header name="Access-Control-Allow-Methods">GET, POST, OPTIONS</Header>
</Headers>
</Set>
</AssignMessage>
<!-- JavaScript Policy - Custom logic -->
<Javascript name="ProcessRequest">
<ResourceURL>jsc://process-request.js</ResourceURL>
</Javascript>
<!-- Service Callout - Call another API -->
<ServiceCallout name="GetUserProfile">
<Request>
<Set>
<Verb>GET</Verb>
<Url>https://users-api.internal/users/{user.id}</Url>
</Set>
</Request>
<Response>user.profile</Response>
</ServiceCallout>Apigee's policy-based approach is powerful but has a learning curve. For simple use cases, Kong or AWS API Gateway may be easier.
When to Use Apigee
- Large enterprise with many APIs
- Need developer portal & self-service
- API monetization is a business model
- Compliance requirements (HIPAA, PCI DSS)
- Multiple teams managing different APIs
- Already using Google Cloud
- Small team with 1-5 APIs
- Simple proxying needs
- Budget-conscious startups
- Need for rapid iteration (XML policies slow down development)
- Greenfield projects (consider Kong, AWS API Gateway)
Pricing: Apigee starts at ~$3,000/month (hybrid deployment). Kong Enterprise is ~$1,000-2,000/month. AWS API Gateway is pay-per-use ($1-3.50 per million requests).
Service Mesh: Istio & Linkerd
A service mesh is a dedicated infrastructure layer for managing service-to-service communication in microservices architectures. Instead of a single gateway, a service mesh deploys sidecar proxies alongside each service. These proxies handle encryption (mTLS), observability, traffic routing, retries, circuit breaking, and more, without changing application code.
API Gateway vs Service Mesh
| Aspect | API Gateway | Service Mesh |
|---|---|---|
| Scope | External client → Backend (north-south traffic) | Service → Service within cluster (east-west traffic) |
| Deployment | Single gateway instance (or cluster) | Sidecar proxy per service instance |
| Use Case | Expose APIs to external consumers, centralize auth | Inter-service communication, mTLS, observability |
| Examples | AWS API Gateway, Kong, Apigee, Azure API Management | Istio, Linkerd, Consul Connect, AWS App Mesh |
| Complexity | Moderate (one component to manage) | High (sidecars everywhere, control plane, certificates) |
You often need both: API Gateway for external traffic (authentication, rate limiting, public-facing endpoints) and Service Mesh for internal traffic (mTLS, retries, circuit breakers, observability between microservices).
Istio - The Most Popular Service Mesh
Istio is a powerful, feature-rich service mesh for Kubernetes. It uses Envoy proxy as the sidecar, providing traffic management, security (mTLS), observability (metrics, traces, logs), and resilience patterns (retries, timeouts, circuit breakers).
- Automatic mTLS: Service-to-service encryption
- Certificate management: Auto-rotation
- Authorization policies: Which service can call which
- Metrics: Prometheus format, request rates, latency
- Distributed tracing: Jaeger/Zipkin integration
- Access logs: Every request logged by Envoy
# Install Istio on Kubernetes
curl -L https://istio.io/downloadIstio | sh -
cd istio-1.24.0
export PATH=$PWD/bin:$PATH
# Install Istio with demo profile
istioctl install --set profile=demo -y
# Label namespace for sidecar injection
kubectl label namespace default istio-injection=enabled
# Deploy your app - Istio auto-injects sidecar
kubectl apply -f your-app.yaml
# VirtualService - Traffic routing
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- match:
- headers:
user:
exact: "john"
route:
- destination:
host: reviews
subset: v2 # John sees v2
- route:
- destination:
host: reviews
subset: v1 # Everyone else sees v1
weight: 90
- destination:
host: reviews
subset: v2
weight: 10 # 10% get v2 (canary)Istio's traffic management is declarative via YAML. VirtualServices define routing, DestinationRules define load balancing and circuit breakers. No code changes needed.
Linkerd - Simple, Lightweight Service Mesh
Linkerd is a CNCF graduated project designed for simplicity and performance. It's lighter weight than Istio, uses a custom Rust-based proxy (linkerd-proxy) instead of Envoy, and is easier to operate. Great for teams that want service mesh benefits without Istio's complexity.
Rust-based proxy, ultra-low latency overhead (<1ms p99)
Minimal config, batteries-included, great CLI
Automatic mTLS, zero-trust by default
# Install Linkerd CLI
curl -sL https://run.linkerd.io/install | sh
export PATH=$PATH:$HOME/.linkerd2/bin
# Check prerequisites
linkerd check --pre
# Install Linkerd on Kubernetes
linkerd install --crds | kubectl apply -f -
linkerd install | kubectl apply -f -
# Verify installation
linkerd check
# Inject sidecar into deployment
kubectl get deploy -o yaml | linkerd inject - | kubectl apply -f -
# Or annotate namespace for auto-injection
kubectl annotate namespace default linkerd.io/inject=enabled
# View dashboard
linkerd dashboard
# Traffic split (canary deployment)
apiVersion: split.smi-spec.io/v1alpha2
kind: TrafficSplit
metadata:
name: reviews-split
spec:
service: reviews
backends:
- service: reviews-v1
weight: 90 # 90% to v1
- service: reviews-v2
weight: 10 # 10% to v2 (canary)Linkerd is opinionated and "just works." Less configuration than Istio. For most teams, Linkerd provides 80% of service mesh benefits with 20% of the complexity.
When Do You Need a Service Mesh?
- 10+ microservices with complex inter-service communication
- Need zero-trust security (mTLS everywhere)
- Struggling with observability (which service is slow?)
- Need advanced traffic management (canary, blue-green)
- Compliance requires encrypted service-to-service traffic
- Running on Kubernetes
- Monolith or <5 microservices
- Simple architecture (API → database)
- Team lacks Kubernetes expertise
- Don't need mTLS (internal network is trusted)
- Observability needs met by APM tools (DataDog, New Relic)
- Just starting with microservices (add mesh later)
Start simple, add complexity as needed. API Gateway first (for external traffic), then service mesh if inter-service complexity grows. Don't adopt service mesh prematurely, it adds operational overhead.
Backend for Frontend (BFF) Pattern
The Backend for Frontend (BFF) pattern creates a dedicated backend for each frontend experience (web, mobile, smart TV). Each BFF aggregates data from multiple microservices, transforming it into the exact format the frontend needs. This reduces over-fetching, under-fetching, and API chattiness.
BFF Architecture
Mobile App
iOS/Android
Web App
React/Vue
Smart TV
Apple TV/Roku
Wearable
Apple Watch
Why Use BFF?
- API chattiness: Mobile makes 10 calls to render one screen
- Over-fetching: Backend returns 50 fields, frontend needs 5
- Under-fetching: Need to make multiple requests for related data
- Device constraints: Mobile needs smaller payloads, fewer requests
- Different needs: Web wants pagination, mobile wants infinite scroll
- Optimized for frontend: Exact data shape needed, no more, no less
- Reduced network calls: BFF aggregates, frontend makes 1 call
- Parallel backend calls: BFF fetches from multiple services concurrently
- Frontend-specific logic: Device detection, A/B testing, formatting
- Decoupled teams: Mobile team owns mobile BFF, web team owns web BFF
BFF Example: Product Detail Page
Mobile app needs: product info, reviews (top 3), stock availability, recommended products. Without BFF, that's 4 API calls. With BFF, 1 call that aggregates everything.
# Mobile BFF endpoint
@app.get("/mobile/products/{product_id}")
async def get_product_for_mobile(product_id: int):
"""
Aggregates data from multiple services into mobile-optimized response.
Makes parallel calls to reduce latency.
"""
# Parallel calls to backend microservices
product, reviews, stock, recommendations = await asyncio.gather(
products_service.get_product(product_id),
reviews_service.get_top_reviews(product_id, limit=3),
inventory_service.get_stock(product_id),
recommendations_service.get_similar(product_id, limit=5)
)
# Transform to mobile-friendly format
return {
"id": product.id,
"name": product.name,
"price": product.price,
"currency": "USD",
"image_url": product.images[0].url, # Mobile: just thumbnail
"rating": product.avg_rating,
"in_stock": stock.available > 0,
"reviews": [
{
"author": r.author_name,
"rating": r.rating,
"text": r.text[:100], # Mobile: truncated
}
for r in reviews
],
"similar_products": [
{"id": p.id, "name": p.name, "image": p.thumbnail}
for p in recommendations
]
}
# Web BFF endpoint - different data shape
@app.get("/web/products/{product_id}")
async def get_product_for_web(product_id: int):
"""
Web needs more detail: full reviews (paginated), all images, detailed specs.
"""
product, reviews, stock = await asyncio.gather(
products_service.get_product(product_id),
reviews_service.get_reviews(product_id, page=1, limit=10),
inventory_service.get_stock(product_id)
)
return {
"id": product.id,
"name": product.name,
"price": product.price,
"currency": "USD",
"images": [img.url for img in product.images], # All images
"description": product.description, # Full description
"specs": product.specifications, # Detailed specs
"rating": product.avg_rating,
"stock_count": stock.available, # Exact count for web
"reviews": {
"items": [
{
"author": r.author_name,
"rating": r.rating,
"text": r.text, # Full text
"date": r.created_at.isoformat(),
}
for r in reviews.items
],
"total": reviews.total,
"page": 1,
}
}Mobile gets minimal data (thumbnail, truncated reviews), web gets full data (all images, full reviews with pagination). Each BFF is optimized for its frontend's needs.
API Composition & Aggregation
API composition combines multiple backend APIs into a single unified response. The gateway or BFF makes parallel requests to microservices, aggregates the results, and returns a composed response to the client. This pattern reduces frontend complexity and network overhead.
Aggregation Strategies
Make multiple independent requests concurrently, wait for all to complete, merge results. Use when: Services don't depend on each other.
user, orders = await asyncio.gather(
get_user(id), get_orders(id)
)
return { user, orders }Make requests in sequence when one depends on another's result.Use when: Need data from first call to make second call.
user = await get_user(id)
# Need user.org_id for next call
org = await get_org(user.org_id)
return { user, org }Return available data even if some services fail. Don't block entire response on one slow/failing service. Use when: Some data is optional.
try:
recommendations = await get_recs()
except:
recommendations = [] # Graceful degradationUse result from one service to enrich another. Common for joins across microservices. Use when: Combining normalized data.
orders = await get_orders(user_id)
# Enrich with product details
for order in orders:
order.product = await get_product(order.product_id)GraphQL as an Alternative
Instead of hand-coding BFFs and aggregation logic, GraphQL provides a query language where clients specify exactly what data they need. The GraphQL server (Apollo Federation, Hasura) handles aggregation automatically.
GET /api/users/123/dashboard
# Returns:
{ user, orders, notifications }
# Mobile needs different data?
# Create /mobile/users/123/dashboardquery {
user(id: 123) {
name
orders { id, total }
notifications { text }
}
}
# Client decides what to fetchTrade-off: GraphQL eliminates BFF duplication but adds complexity (schema stitching, federation, N+1 queries). Use GraphQL if you have many clients with diverse data needs. Use REST BFFs for simpler cases.
Request Routing & Transformation
API gateways route requests to appropriate backend services and can transform requests/responses in flight. Routing rules based on path, headers, query params, or custom logic. Transformations include header manipulation, payload modification, protocol translation (REST → gRPC).
Common Routing Patterns
| Pattern | Example | Use Case |
|---|---|---|
| Path-based | /api/users/* → users-service/api/orders/* → orders-service | Most common. Route by URL path prefix. |
| Header-based | X-API-Version: v2 → v2 backendX-Tenant: acme → acme cluster | API versioning, multi-tenancy |
| Query param | ?version=beta → beta backend | A/B testing, feature flags |
| Method-based | GET /items → read servicePOST /items → write service | CQRS (separate read/write services) |
| Weight-based | 90% → stable, 10% → canary | Canary deployments, gradual rollout |
| Geographic | EU users → eu-west-1 US users → us-east-1 | Data locality, GDPR compliance |
Transformation Examples
- Add
X-Forwarded-Forwith client IP - Add
X-Request-IDfor tracing - Remove internal headers before response
- Add CORS headers
# Kong plugin
config.add.headers:
- X-Service: api-gateway
- X-Request-ID: \$request_id- Convert XML → JSON
- Rename fields (snake_case → camelCase)
- Remove sensitive fields from response
- Add computed fields
# Response transformer
{ "user_id": 123 }
→ transforms to →
{ "userId": 123 }- REST → gRPC (for internal services)
- HTTP/1.1 → HTTP/2
- SOAP → REST
# Envoy gRPC transcoding
GET /users/123
→ gRPC UsersService.GetUser(id=123)
→ JSON response- Validate JWT, extract user ID to header
- Look up API key, add rate limit tier
- Add tenant ID from auth context
# After JWT validation
Add header: X-User-ID: 123
Add header: X-Tenant: acme
Backend sees user contextWhen to Use Transformations
- Add cross-cutting headers (request ID, tracing)
- CORS handling for browsers
- Simple field renaming (API versioning)
- Remove internal/sensitive fields from responses
- Protocol translation (REST to gRPC for backends)
- Complex business logic (belongs in services)
- Heavy transformations (slow, hard to debug)
- Stateful operations (aggregation with DB lookups)
- Anything that requires unit tests (use Lambda/service instead)
Rule of thumb: If transformation logic needs more than 5 lines of code, move it to a service or Lambda function. Keep gateway logic simple and stateless.
Choosing the Right Solution
| Solution | Best For | Cost | Complexity | Hosting |
|---|---|---|---|---|
| AWS API Gateway | AWS-centric, serverless, simple proxying | $1-3.50 per 1M requests | Low | Fully managed |
| Kong (OSS) | Kubernetes, high performance, plugin ecosystem | Free (self-hosted) | Medium | Self-hosted |
| Kong Enterprise | Enterprise features, support, dev portal | ~$1-2k/month | Medium | Hybrid |
| Apigee | Large orgs, API monetization, compliance | ~$3k+/month | High | Fully managed (GCP) |
| Istio | Microservices on Kubernetes, mTLS, observability | Free (infra costs) | High | Self-hosted (K8s) |
| Linkerd | Simple service mesh, low latency, Kubernetes | Free (infra costs) | Medium | Self-hosted (K8s) |
Decision Tree
- Are you on AWS with serverless backends (Lambda)?
→ Yes: Use AWS API Gateway HTTP API (cheapest, simplest)
→ No: Continue - Do you have 10+ microservices on Kubernetes?
→ Yes: Consider service mesh (Linkerd for simplicity, Istio for features)
→ No: Continue - Do you need developer portal, API monetization, or enterprise compliance?
→ Yes: Use Apigee (if on GCP) or Kong Enterprise
→ No: Continue - Do you need high performance, plugin ecosystem, and self-hosting?
→ Yes: Use Kong (open source)
→ No: Continue - Simple use case, just need auth + rate limiting + routing?
→ Use managed gateway: AWS API Gateway, Azure API Management, or Google Cloud Endpoints
Key Takeaways
- API gateways centralize cross-cutting concerns like authentication, rate limiting, logging, and routing. Backend services stay simple and focused on business logic.
- AWS API Gateway is simple and cost-effective for serverless and AWS-centric architectures. HTTP API is 71% cheaper than REST API for simple proxying.
- Kong provides high performance and extensibility via plugins. Open-source version is free, Enterprise adds developer portal and support. Great for Kubernetes.
- Apigee is enterprise-grade with API monetization, analytics, compliance, and developer portals. Best for large organizations with hundreds of APIs.
- Service mesh (Istio, Linkerd) handles service-to-service communicationin microservices. Provides mTLS, observability, traffic management without code changes. Use only if you have 10+ microservices on Kubernetes.
- BFF pattern creates dedicated backends for each frontend, reducing API chattiness and over-fetching. Each BFF aggregates data optimized for its client (mobile, web, TV).
- API composition aggregates multiple backend calls into a single response. Use parallel aggregation for independent services, sequential for dependent calls.
- Request routing and transformation enable versioning, A/B testing, canary deployments, and protocol translation. Keep transformations simple, complex logic belongs in services.
Ready to Build?
Start simple: deploy an API gateway for authentication and rate limiting. As your architecture grows, add BFF layers for mobile/web optimization. If you reach 10+ microservices, evaluate service mesh for mTLS and observability.
Pro tip: Don't over-engineer early. Most teams only need an API gateway (AWS API Gateway, Kong). Service mesh is powerful but complex, adopt it when inter-service communication becomes a bottleneck. Measure first, optimize later.