# API Gateway

## Problem

In a microservice architecture, clients must know the addresses of multiple backend services, handle different protocols, and implement cross-cutting concerns (authentication, rate limiting, logging) independently. This leads to tight coupling between clients and services, duplicated logic, and complex client code.

## Solution

Place an **API Gateway** as the single entry point for all client requests. The gateway:

- **Routes** requests to the appropriate backend service based on path, headers, or other rules.
- **Aggregates** responses from multiple services into a single response when needed.
- Handles **cross-cutting concerns**: authentication, rate limiting, CORS, logging, request transformation.
- **Decouples** clients from service topology — clients talk to one URL.

## When to Use

- Multiple backend services need to be exposed through a unified API.
- Cross-cutting concerns (auth, rate limiting, logging) should be centralised.
- Clients should be insulated from service discovery and topology changes.
- You need protocol translation (e.g., REST to gRPC).

## When to Avoid

- Single-service architectures where a gateway adds unnecessary latency and complexity.
- Internal service-to-service communication (use service mesh instead).
- When the gateway becomes a monolithic bottleneck — keep it thin.

## Pseudocode

```
class APIGateway:
    routes = {
        "/users/*":    "http://user-service:8001",
        "/orders/*":   "http://order-service:8002",
        "/products/*": "http://product-service:8003",
    }

    function handle_request(request):
        if not authenticate(request):
            return 401, "Unauthorized"

        if rate_limited(request):
            return 429, "Too Many Requests"

        backend = match_route(request.path)
        if backend is null:
            return 404, "Not Found"

        response = forward(request, backend)
        log_request(request, response)
        return response
```

## Python

```python
"""API Gateway pattern in Python — simulated routing and middleware."""
import time
from dataclasses import dataclass, field


# ── Request / Response models ─────────────────────────────────

@dataclass
class Request:
    method: str
    path: str
    headers: dict = field(default_factory=dict)
    body: str = ""


@dataclass
class Response:
    status: int
    body: str
    headers: dict = field(default_factory=dict)


# ── Simulated backend services ────────────────────────────────

class UserService:
    def handle(self, req: Request) -> Response:
        if req.path == "/users/1":
            return Response(200, '{"id":1,"name":"Alice"}')
        return Response(404, '{"error":"user not found"}')


class OrderService:
    def handle(self, req: Request) -> Response:
        if req.path == "/orders/latest":
            return Response(200, '{"id":101,"item":"Widget","total":29.99}')
        return Response(404, '{"error":"order not found"}')


# ── API Gateway ───────────────────────────────────────────────

class APIGateway:
    def __init__(self):
        self.routes: dict[str, object] = {}
        self.request_log: list[dict] = []
        self._rate_counts: dict[str, list[float]] = {}

    def register(self, prefix: str, service: object) -> None:
        self.routes[prefix] = service

    def _authenticate(self, req: Request) -> bool:
        token = req.headers.get("Authorization", "")
        return token.startswith("Bearer ")

    def _rate_limited(self, req: Request, max_per_sec: int = 5) -> bool:
        client_ip = req.headers.get("X-Client-IP", "unknown")
        now = time.time()
        window = self._rate_counts.setdefault(client_ip, [])
        # Clean old entries
        window[:] = [t for t in window if now - t < 1.0]
        if len(window) >= max_per_sec:
            return True
        window.append(now)
        return False

    def _match_route(self, path: str):
        for prefix, service in self.routes.items():
            if path.startswith(prefix):
                return service
        return None

    def handle(self, req: Request) -> Response:
        # Middleware: authentication
        if not self._authenticate(req):
            return Response(401, '{"error":"Unauthorized"}')

        # Middleware: rate limiting
        if self._rate_limited(req):
            return Response(429, '{"error":"Too Many Requests"}')

        # Routing
        service = self._match_route(req.path)
        if service is None:
            return Response(404, '{"error":"Not Found"}')

        # Forward to backend
        response = service.handle(req)

        # Logging
        self.request_log.append({
            "method": req.method, "path": req.path,
            "status": response.status,
        })

        return response


# ── Tests ─────────────────────────────────────────────────────

def create_gateway() -> APIGateway:
    gw = APIGateway()
    gw.register("/users", UserService())
    gw.register("/orders", OrderService())
    return gw


def test_routes_to_user_service():
    gw = create_gateway()
    resp = gw.handle(Request("GET", "/users/1", {"Authorization": "Bearer tok123"}))
    assert resp.status == 200 and "Alice" in resp.body
    print(f"PASS: /users/1 -> {resp.status} {resp.body}")


def test_routes_to_order_service():
    gw = create_gateway()
    resp = gw.handle(Request("GET", "/orders/latest", {"Authorization": "Bearer tok"}))
    assert resp.status == 200 and "Widget" in resp.body
    print(f"PASS: /orders/latest -> {resp.status} {resp.body}")


def test_unauthorized_without_token():
    gw = create_gateway()
    resp = gw.handle(Request("GET", "/users/1"))
    assert resp.status == 401
    print(f"PASS: no token -> {resp.status} {resp.body}")


def test_unknown_route_returns_404():
    gw = create_gateway()
    resp = gw.handle(Request("GET", "/unknown", {"Authorization": "Bearer tok"}))
    assert resp.status == 404
    print(f"PASS: unknown route -> {resp.status} {resp.body}")


def test_request_logging():
    gw = create_gateway()
    gw.handle(Request("GET", "/users/1", {"Authorization": "Bearer tok"}))
    gw.handle(Request("GET", "/orders/latest", {"Authorization": "Bearer tok"}))
    assert len(gw.request_log) == 2
    print(f"PASS: logged {len(gw.request_log)} requests: {gw.request_log}")


if __name__ == "__main__":
    test_routes_to_user_service()
    test_routes_to_order_service()
    test_unauthorized_without_token()
    test_unknown_route_returns_404()
    test_request_logging()
    print("\nAll API Gateway tests passed.")
```

## JavaScript

```javascript
// api_gateway.js — API Gateway pattern in JavaScript

// ── Simulated backend services ───────────────────────────────

class UserService {
  handle(req) {
    if (req.path === "/users/1")
      return { status: 200, body: '{"id":1,"name":"Alice"}' };
    return { status: 404, body: '{"error":"user not found"}' };
  }
}

class OrderService {
  handle(req) {
    if (req.path === "/orders/latest")
      return { status: 200, body: '{"id":101,"item":"Widget","total":29.99}' };
    return { status: 404, body: '{"error":"order not found"}' };
  }
}

// ── API Gateway ──────────────────────────────────────────────

class APIGateway {
  constructor() {
    this.routes = new Map();
    this.requestLog = [];
  }

  register(prefix, service) {
    this.routes.set(prefix, service);
  }

  _authenticate(req) {
    const token = (req.headers || {})["Authorization"] || "";
    return token.startsWith("Bearer ");
  }

  _matchRoute(path) {
    for (const [prefix, service] of this.routes) {
      if (path.startsWith(prefix)) return service;
    }
    return null;
  }

  handle(req) {
    // Auth middleware
    if (!this._authenticate(req)) {
      return { status: 401, body: '{"error":"Unauthorized"}' };
    }

    // Routing
    const service = this._matchRoute(req.path);
    if (!service) {
      return { status: 404, body: '{"error":"Not Found"}' };
    }

    // Forward
    const response = service.handle(req);

    // Log
    this.requestLog.push({
      method: req.method,
      path: req.path,
      status: response.status,
    });

    return response;
  }
}

// ── Tests ────────────────────────────────────────────────────

function createGateway() {
  const gw = new APIGateway();
  gw.register("/users", new UserService());
  gw.register("/orders", new OrderService());
  return gw;
}

function testRoutesToUserService() {
  const gw = createGateway();
  const resp = gw.handle({
    method: "GET",
    path: "/users/1",
    headers: { Authorization: "Bearer tok" },
  });
  console.assert(resp.status === 200 && resp.body.includes("Alice"));
  console.log(`PASS: /users/1 -> ${resp.status} ${resp.body}`);
}

function testRoutesToOrderService() {
  const gw = createGateway();
  const resp = gw.handle({
    method: "GET",
    path: "/orders/latest",
    headers: { Authorization: "Bearer tok" },
  });
  console.assert(resp.status === 200 && resp.body.includes("Widget"));
  console.log(`PASS: /orders/latest -> ${resp.status} ${resp.body}`);
}

function testUnauthorized() {
  const gw = createGateway();
  const resp = gw.handle({ method: "GET", path: "/users/1", headers: {} });
  console.assert(resp.status === 401);
  console.log(`PASS: no token -> ${resp.status} ${resp.body}`);
}

function testUnknownRoute() {
  const gw = createGateway();
  const resp = gw.handle({
    method: "GET",
    path: "/unknown",
    headers: { Authorization: "Bearer tok" },
  });
  console.assert(resp.status === 404);
  console.log(`PASS: unknown -> ${resp.status} ${resp.body}`);
}

function testRequestLogging() {
  const gw = createGateway();
  gw.handle({
    method: "GET",
    path: "/users/1",
    headers: { Authorization: "Bearer tok" },
  });
  gw.handle({
    method: "GET",
    path: "/orders/latest",
    headers: { Authorization: "Bearer tok" },
  });
  console.assert(gw.requestLog.length === 2);
  console.log(
    `PASS: logged ${gw.requestLog.length} requests: ${JSON.stringify(gw.requestLog)}`
  );
}

testRoutesToUserService();
testRoutesToOrderService();
testUnauthorized();
testUnknownRoute();
testRequestLogging();
console.log("\nAll API Gateway tests passed.");
```

## Related Patterns

- **Facade** — the API Gateway is a facade over multiple backend services, presenting a unified API to clients.
- **Rate Limiter** — the gateway enforces per-client rate limits as a cross-cutting concern.
- **Circuit Breaker** — the gateway can wrap backend calls in circuit breakers to prevent cascading failures.
- **Backend-for-Frontend** — a BFF is a specialised API Gateway tailored for one specific client type.
- **Sidecar** — the gateway handles cross-cutting concerns at the network edge; sidecar handles them at the service instance level.
