Skip to content

API Gateway

What is an API Gateway?

An API Gateway is a server that acts as a single entry point for a set of microservices, handling cross-cutting concerns like authentication, rate limiting, routing, and protocol translation.

API Gateway Overview


Gateway Type Best For
AWS API Gateway Managed AWS ecosystem, serverless
Kong Open Source/Enterprise Kubernetes, plugins
NGINX Open Source High performance
Envoy Open Source Service mesh, Kubernetes
Apigee Enterprise (Google) API management, analytics
Azure API Management Managed Azure ecosystem
Spring Cloud Gateway Framework Spring Boot apps
Traefik Open Source Docker, Kubernetes
Ambassador Open Source Kubernetes-native

Core Features

Core Features


Architecture Patterns

Single Gateway

Single Gateway

Backend for Frontend (BFF)

Backend for Frontend Pattern

Gateway with Mesh

Gateway with Mesh


Common Use Cases

1. Authentication & Authorization

# Kong configuration example
plugins:
  - name: jwt
    config:
      secret_is_base64: false
      claims_to_verify:
        - exp
        - nbf

  - name: acl
    config:
      whitelist:
        - admin
        - user
// Spring Cloud Gateway
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("auth_route", r -> r
            .path("/api/**")
            .filters(f -> f
                .filter(new JwtAuthenticationFilter())
                .filter(new RoleAuthorizationFilter("ADMIN")))
            .uri("lb://backend-service"))
        .build();
}

2. Rate Limiting

# Kong rate limiting
plugins:
  - name: rate-limiting
    config:
      minute: 100
      hour: 10000
      policy: local  # or redis for distributed
      fault_tolerant: true
// Spring Cloud Gateway with Redis
@Bean
public RedisRateLimiter redisRateLimiter() {
    return new RedisRateLimiter(10, 20);  // 10 req/sec, burst of 20
}

@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("rate_limited", r -> r
            .path("/api/**")
            .filters(f -> f.requestRateLimiter(c -> c
                .setRateLimiter(redisRateLimiter())
                .setKeyResolver(exchange ->
                    Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress()))))
            .uri("lb://backend"))
        .build();
}

3. Request Routing

# NGINX configuration
upstream user_service {
    server user-svc-1:8080;
    server user-svc-2:8080;
}

upstream order_service {
    server order-svc-1:8080;
    server order-svc-2:8080;
}

server {
    listen 80;

    location /api/users {
        proxy_pass http://user_service;
    }

    location /api/orders {
        proxy_pass http://order_service;
    }

    # Canary routing
    location /api/v2/users {
        proxy_pass http://user_service_v2;
    }
}
# Kong routing
services:
  - name: user-service
    url: http://user-svc:8080

routes:
  - name: users-route
    service: user-service
    paths:
      - /api/users
    strip_path: false

4. Request/Response Transformation

# Kong request transformer
plugins:
  - name: request-transformer
    config:
      add:
        headers:
          - X-Request-ID:$(uuid)
          - X-Forwarded-Service:user-service
      remove:
        headers:
          - X-Internal-Header
// Spring Cloud Gateway
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("transform", r -> r
            .path("/api/**")
            .filters(f -> f
                .addRequestHeader("X-Request-ID", UUID.randomUUID().toString())
                .removeRequestHeader("X-Internal-Header")
                .modifyResponseBody(String.class, String.class,
                    (exchange, body) -> Mono.just(wrapResponse(body))))
            .uri("lb://backend"))
        .build();
}

5. API Versioning

# Path-based versioning
routes:
  - name: users-v1
    paths: ["/v1/users"]
    service: user-service-v1

  - name: users-v2
    paths: ["/v2/users"]
    service: user-service-v2

# Header-based versioning
  - name: users-v1
    paths: ["/users"]
    headers:
      API-Version: "1"
    service: user-service-v1

  - name: users-v2
    paths: ["/users"]
    headers:
      API-Version: "2"
    service: user-service-v2

6. Circuit Breaker

// Spring Cloud Gateway with Resilience4j
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
    return builder.routes()
        .route("circuit_breaker", r -> r
            .path("/api/**")
            .filters(f -> f
                .circuitBreaker(config -> config
                    .setName("backendCircuitBreaker")
                    .setFallbackUri("forward:/fallback")))
            .uri("lb://backend"))
        .build();
}

@RestController
public class FallbackController {
    @GetMapping("/fallback")
    public ResponseEntity<String> fallback() {
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
            .body("Service temporarily unavailable");
    }
}
# Kong circuit breaker
plugins:
  - name: circuit-breaker
    config:
      threshold: 5
      window_size: 60
      timeout: 30
      half_open_after: 30

7. Caching

# Kong caching
plugins:
  - name: proxy-cache
    config:
      response_code:
        - 200
      request_method:
        - GET
        - HEAD
      content_type:
        - application/json
      cache_ttl: 300
      strategy: memory
# NGINX caching
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m
                 max_size=100m inactive=60m use_temp_path=off;

server {
    location /api/ {
        proxy_cache api_cache;
        proxy_cache_valid 200 5m;
        proxy_cache_key "$scheme$request_method$host$request_uri";
        add_header X-Cache-Status $upstream_cache_status;
        proxy_pass http://backend;
    }
}

8. SSL/TLS Termination

server {
    listen 443 ssl http2;
    server_name api.example.com;

    ssl_certificate /etc/ssl/certs/api.crt;
    ssl_certificate_key /etc/ssl/private/api.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass http://backend;  # HTTP to backend
    }
}

AWS API Gateway

REST API

# SAM template
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  MyApi:
    Type: AWS::Serverless::Api
    Properties:
      StageName: prod
      Auth:
        DefaultAuthorizer: MyCognitoAuthorizer
        Authorizers:
          MyCognitoAuthorizer:
            UserPoolArn: !GetAtt MyUserPool.Arn

  GetUserFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: users.get
      Runtime: nodejs18.x
      Events:
        GetUser:
          Type: Api
          Properties:
            RestApiId: !Ref MyApi
            Path: /users/{id}
            Method: GET

HTTP API (Faster, Cheaper)

Resources:
  HttpApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      StageName: prod
      CorsConfiguration:
        AllowOrigins:
          - "https://example.com"
        AllowMethods:
          - GET
          - POST

Features Comparison

Feature REST API HTTP API
Latency Higher ~60% lower
Cost Higher ~70% cheaper
API Keys Yes No
Usage Plans Yes No
Caching Yes No
Request Validation Yes No
WAF Yes No
Private APIs Yes Yes
Lambda Integration Yes Yes
JWT Auth Custom Native

Kong Gateway

Docker Compose Setup

version: '3'
services:
  kong-database:
    image: postgres:13
    environment:
      POSTGRES_USER: kong
      POSTGRES_DB: kong
      POSTGRES_PASSWORD: kong

  kong-migration:
    image: kong:latest
    command: kong migrations bootstrap
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_PASSWORD: kong
    depends_on:
      - kong-database

  kong:
    image: kong:latest
    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
      - "8001:8001"  # Admin API
    depends_on:
      - kong-migration

Declarative Configuration

# kong.yml
_format_version: "2.1"

services:
  - name: user-service
    url: http://user-svc:8080
    routes:
      - name: users
        paths:
          - /api/users
        strip_path: false
    plugins:
      - name: rate-limiting
        config:
          minute: 100
      - name: jwt
        config:
          secret_is_base64: false

  - name: order-service
    url: http://order-svc:8080
    routes:
      - name: orders
        paths:
          - /api/orders

consumers:
  - username: mobile-app
    plugins:
      - name: rate-limiting
        config:
          minute: 1000

Spring Cloud Gateway

Dependencies

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Configuration

# application.yml
spring:
  cloud:
    gateway:
      routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/api/users/**
          filters:
            - StripPrefix=1
            - AddRequestHeader=X-Request-ID, ${random.uuid}

        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
            - Header=Authorization, Bearer.*
          filters:
            - StripPrefix=1
            - CircuitBreaker=name=orderCircuitBreaker,fallbackUri=forward:/fallback

      default-filters:
        - DedupeResponseHeader=Access-Control-Allow-Origin

      globalcors:
        corsConfigurations:
          '[/**]':
            allowedOrigins: "https://example.com"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE

Custom Filter

@Component
public class AuthenticationFilter implements GatewayFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        // Check Authorization header
        if (!request.getHeaders().containsKey("Authorization")) {
            return onError(exchange, "No Authorization header", HttpStatus.UNAUTHORIZED);
        }

        String authHeader = request.getHeaders().getFirst("Authorization");
        if (!authHeader.startsWith("Bearer ")) {
            return onError(exchange, "Invalid Authorization header", HttpStatus.UNAUTHORIZED);
        }

        String token = authHeader.substring(7);
        try {
            Claims claims = validateToken(token);

            // Add user info to headers for downstream services
            ServerHttpRequest modifiedRequest = request.mutate()
                .header("X-User-ID", claims.getSubject())
                .header("X-User-Roles", claims.get("roles", String.class))
                .build();

            return chain.filter(exchange.mutate().request(modifiedRequest).build());

        } catch (Exception e) {
            return onError(exchange, "Invalid token", HttpStatus.UNAUTHORIZED);
        }
    }

    private Mono<Void> onError(ServerWebExchange exchange, String message, HttpStatus status) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(status);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        return -100; // Run early in filter chain
    }
}

Trade-offs

Pros Cons
Single entry point Single point of failure
Cross-cutting concerns centralized Additional latency
Protocol translation Added complexity
Security at the edge Can become bottleneck
Simplified clients Another component to manage
Traffic management Potential coupling

Performance Considerations

Factor Impact
Connection pooling Critical for backend connections
Caching Reduces backend load
Compression Reduces bandwidth
HTTP/2 Multiplexing, header compression
SSL session caching Reduces handshake overhead
Rate limiting Prevents overload
Timeouts Prevents hanging connections

When to Use API Gateway

Good For: - Microservices architecture - Multi-client applications (web, mobile, IoT) - Third-party API exposure - Legacy modernization - Cross-cutting concerns

Consider Alternatives When: - Simple monolith (use reverse proxy) - Service mesh already handles concerns - Very latency-sensitive (direct communication) - Simple internal APIs


Best Practices

  1. Keep it thin - Business logic in services, not gateway
  2. Avoid single point of failure - Deploy multiple instances
  3. Cache aggressively - Reduce backend load
  4. Monitor everything - Latency, errors, throughput
  5. Rate limit early - Protect backend services
  6. Use circuit breakers - Fail fast, recover gracefully
  7. Validate requests - Block bad requests at the edge
  8. Version APIs - Support multiple versions
  9. Log requests - For debugging and analytics
  10. Use async when possible - Non-blocking I/O

Monitoring & Observability

# Kong Prometheus plugin
plugins:
  - name: prometheus
    config:
      status_code_metrics: true
      latency_metrics: true
      bandwidth_metrics: true
      upstream_health_metrics: true

Key Metrics to Monitor: - Request rate - Error rate (4xx, 5xx) - Latency (p50, p95, p99) - Upstream health - Connection pool usage - Rate limit hits - Circuit breaker state - Cache hit ratio