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.

Without API Gateway
  • 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
With API Gateway
  • 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

REST API

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
HTTP API

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!)
WebSocket API

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
}
Note:VTL is powerful but hard to maintain. For complex transformations, consider using Lambda (easier to test and debug). HTTP API doesn't support VTL, transformations must happen in your backend.

Throttling, Caching & Usage Plans

Throttling

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 concurrent
Caching

Cache 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: prod

Kong 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

Client

Makes requests to Kong

Kong Gateway

Applies plugins, routes requests

Upstream Service

Your backend API

Core Concepts:
  • 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:8000

Configuring 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

PluginPurposeUse Case
jwtJWT authenticationVerify JWT tokens, extract claims
key-authAPI key authenticationSimple auth via API keys in headers
rate-limitingRate limiting100 requests/minute per consumer
corsCORS handlingAllow cross-origin requests from browsers
request-transformerTransform requestsAdd/remove headers, query params, body
response-transformerTransform responsesModify response headers, body
prometheusMetrics exportExpose metrics for Prometheus scraping
http-logLoggingSend request/response logs to HTTP endpoint
proxy-cacheResponse cachingCache GET responses, reduce backend load
oauth2OAuth 2.0 providerKong 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

Developer Portal

Self-service portal for external developers. API documentation, API keys, usage analytics, forums. Onboard partners and third-party developers easily.

API Monetization

Charge for API usage. Define rate plans, tiered pricing, usage quotas. Generate invoices, track revenue. Perfect for API-as-a-product businesses.

Advanced Analytics

Pre-built dashboards for traffic, errors, latency, top consumers. Custom reports, anomaly detection. Export to BigQuery for deep analysis.

Advanced Security

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

✅ Good Fit:
  • 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
❌ Overkill For:
  • 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

AspectAPI GatewayService Mesh
ScopeExternal client → Backend (north-south traffic)Service → Service within cluster (east-west traffic)
DeploymentSingle gateway instance (or cluster)Sidecar proxy per service instance
Use CaseExpose APIs to external consumers, centralize authInter-service communication, mTLS, observability
ExamplesAWS API Gateway, Kong, Apigee, Azure API ManagementIstio, Linkerd, Consul Connect, AWS App Mesh
ComplexityModerate (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).

Security
  • Automatic mTLS: Service-to-service encryption
  • Certificate management: Auto-rotation
  • Authorization policies: Which service can call which
Observability
  • 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.

Fast

Rust-based proxy, ultra-low latency overhead (<1ms p99)

Simple

Minimal config, batteries-included, great CLI

Secure

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?

✅ You Need It If:
  • 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
❌ You Don't If:
  • 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

Mobile BFF
Web BFF
TV BFF
Wearable BFF
Users Service
Products Service
Orders Service
Reviews Service
Inventory Service

Why Use BFF?

Problems BFF Solves
  • 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
BFF Benefits
  • 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

Parallel Aggregation

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 }
Sequential Aggregation

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 }
Partial Response

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 degradation
Chained Aggregation

Use 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.

REST BFF (manual aggregation):
GET /api/users/123/dashboard

# Returns:
{ user, orders, notifications }

# Mobile needs different data?
# Create /mobile/users/123/dashboard
GraphQL (client-driven):
query {
  user(id: 123) {
    name
    orders { id, total }
    notifications { text }
  }
}

# Client decides what to fetch

Trade-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

PatternExampleUse Case
Path-based/api/users/* → users-service
/api/orders/* → orders-service
Most common. Route by URL path prefix.
Header-basedX-API-Version: v2 → v2 backend
X-Tenant: acme → acme cluster
API versioning, multi-tenancy
Query param?version=beta → beta backendA/B testing, feature flags
Method-basedGET /items → read service
POST /items → write service
CQRS (separate read/write services)
Weight-based90% → stable, 10% → canaryCanary deployments, gradual rollout
GeographicEU users → eu-west-1
US users → us-east-1
Data locality, GDPR compliance

Transformation Examples

Header Manipulation
  • Add X-Forwarded-For with client IP
  • Add X-Request-ID for tracing
  • Remove internal headers before response
  • Add CORS headers
# Kong plugin
config.add.headers:
  - X-Service: api-gateway
  - X-Request-ID: \$request_id
Payload Modification
  • 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 }
Protocol Translation
  • 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
Security Enrichment
  • 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 context

When to Use Transformations

✅ Good Use Cases:
  • 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)
❌ Avoid:
  • 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

SolutionBest ForCostComplexityHosting
AWS API GatewayAWS-centric, serverless, simple proxying$1-3.50 per 1M requestsLowFully managed
Kong (OSS)Kubernetes, high performance, plugin ecosystemFree (self-hosted)MediumSelf-hosted
Kong EnterpriseEnterprise features, support, dev portal~$1-2k/monthMediumHybrid
ApigeeLarge orgs, API monetization, compliance~$3k+/monthHighFully managed (GCP)
IstioMicroservices on Kubernetes, mTLS, observabilityFree (infra costs)HighSelf-hosted (K8s)
LinkerdSimple service mesh, low latency, KubernetesFree (infra costs)MediumSelf-hosted (K8s)

Decision Tree

  1. Are you on AWS with serverless backends (Lambda)?
    → Yes: Use AWS API Gateway HTTP API (cheapest, simplest)
    → No: Continue
  2. Do you have 10+ microservices on Kubernetes?
    → Yes: Consider service mesh (Linkerd for simplicity, Istio for features)
    → No: Continue
  3. Do you need developer portal, API monetization, or enterprise compliance?
    → Yes: Use Apigee (if on GCP) or Kong Enterprise
    → No: Continue
  4. Do you need high performance, plugin ecosystem, and self-hosting?
    → Yes: Use Kong (open source)
    → No: Continue
  5. 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.