The most surprising thing about API Gateways is that they often aren’t the bottleneck you expect them to be, even when handling massive traffic.

Let’s see it in action. Imagine a simple frontend making a request to get user data.

GET /users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

This request hits an API Gateway running on api.example.com. The gateway, configured with specific rules, will:

  1. Authenticate the request: It sees the Authorization header, extracts the JWT, and verifies its signature against a known public key. If valid, it extracts the user ID (in this case, "1234567890").
  2. Authorize the request: It checks if the authenticated user has permission to access /users/123. This might involve looking up roles or scopes associated with the JWT.
  3. Route the request: If authentication and authorization pass, the gateway looks at its routing rules. It might see that requests to /users/* should be forwarded to a dedicated user-service running on http://user-service:8080.
  4. Forward the request: The gateway then makes a new request to http://user-service:8080/users/123, potentially adding headers like X-Forwarded-For or X-User-ID derived from the original request.
  5. Receive the response: The user-service processes the request and sends a response back to the gateway.
  6. Return the response: The gateway relays this response back to the original client.

This entire process is managed by the gateway, acting as a single entry point. It simplifies the client’s interaction – they only need to know about api.example.com. It also decouples clients from the internal microservice architecture. If user-service is moved to a new port or replaced, the client doesn’t need to change its configuration.

The core problem this solves is the distributed complexity of managing cross-cutting concerns like authentication, rate limiting, and logging across many independent microservices. Instead of each service implementing its own auth logic, the gateway handles it once. Routing is also centralized, so services don’t need to know about each other’s network locations.

Let’s look at a common configuration for a gateway like Kong or Traefik.

Routing Example (Conceptual - Kong routes object):

{
  "name": "users-route",
  "paths": ["/users", "/users/*"],
  "strip_path": true,
  "preserve_host": false,
  "service": {
    "name": "user-service",
    "host": "user-service.internal",
    "port": 8080
  }
}

This configuration tells the gateway: "Any request matching /users or /users/* should be sent to the user-service which can be found at user-service.internal:8080. When forwarding, remove the /users prefix from the path."

Authentication Example (Conceptual - Kong plugins attached to the route):

{
  "name": "jwt",
  "config": {
    "key_claims": ["sub"],
    "secret_verify": true,
    "jws_algos": ["RS256"],
    "rsa_public_key": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
  }
}

This plugin configures the gateway to expect a JWT in the Authorization header. It will verify the signature using the provided RSA public key and extract the subject (sub) claim.

The power here is that you can add other plugins to this same route. For instance, a rate-limiting plugin can be added to prevent abuse of the /users endpoint, or a request-transformer plugin could add custom headers before forwarding to the backend.

A common pattern is to have a dedicated authentication service. The API Gateway can be configured to call this auth service first for every incoming request. The auth service validates the token and returns an allow or deny decision, along with user context (like user ID, roles, etc.). This context can then be injected as headers into the request before it’s forwarded to the target microservice. This way, the microservices themselves don’t need to deal with token validation at all; they just trust the headers provided by the gateway.

Many people configure their API Gateway with a simple load balancer in front of it for high availability, but this can lead to issues if the gateway itself becomes the single point of failure for all traffic. The more robust approach is to have multiple gateway instances running, behind a highly available load balancer or using a distributed gateway solution where instances can coordinate state (like routing rules or rate limit counters).

The real magic happens when you realize that the API Gateway can also act as a protocol translator. It can accept requests over HTTP/1.1 and forward them to backend services using HTTP/2, or vice-versa. It can also perform response transformations – perhaps a backend service returns XML, but the client expects JSON. The gateway can convert this on the fly.

The next hurdle is managing configuration across multiple environments and ensuring consistent routing and policy enforcement as your microservice landscape grows and changes.

Want structured learning?

Take the full Microservices course →