Language:English VersionChinese Version

Your API Gateway Might Be Over-Engineering a Simple Problem

API gateways promise centralized authentication, rate limiting, routing, observability, and protocol translation. In practice, they’re also one of the most commonly over-provisioned pieces of infrastructure — a full Kong or Envoy deployment for an application that handles 200 requests per second is like using a freight elevator to move a suitcase. This guide covers what API gateways actually do, when you need one, when a simpler reverse proxy suffices, and when the major options (Kong, Envoy, Traefik) make sense.

What an API Gateway Actually Does

An API gateway sits in front of your services and handles cross-cutting concerns that would otherwise need to be implemented in every service:

  • Authentication and authorization: Validate JWT tokens, API keys, OAuth flows before requests reach your services
  • Rate limiting: Per-user, per-IP, or per-key request throttling
  • Request routing: Route /v1/users to the user service, /v1/orders to the order service
  • Protocol transformation: REST-to-gRPC, REST-to-GraphQL aggregation
  • Observability: Centralized access logs, metrics, distributed tracing initiation
  • Request/response transformation: Add headers, transform payloads, aggregate responses
  • SSL termination: Handle TLS in one place rather than every service

Do You Actually Need an API Gateway?

Before evaluating tools, ask whether you need an API gateway at all. You probably don’t if:

  • You have fewer than 5 backend services
  • You’re running a monolith with a single deployment
  • Your authentication is handled entirely within your application
  • You’re not exposing a public API to third-party developers

What you likely need instead: a well-configured reverse proxy (Nginx or Caddy) plus application-level middleware.

# Nginx as a simple "gateway" for a 3-service application
# This handles 80% of what small teams need from an API gateway

upstream user_service {
    server user-service:8001;
    keepalive 32;
}
upstream order_service {
    server order-service:8002;
    keepalive 32;
}
upstream payment_service {
    server payment-service:8003;
    keepalive 32;
}

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

    # Rate limiting (simple, built into Nginx)
    limit_req_zone $binary_remote_addr zone=api:10m rate=100r/m;
    limit_req zone=api burst=20 nodelay;

    # Route by path prefix
    location /v1/users/ {
        proxy_pass http://user_service/;
        proxy_set_header X-Request-ID $request_id;
        proxy_set_header Authorization $http_authorization;
    }

    location /v1/orders/ {
        proxy_pass http://order_service/;
        proxy_set_header X-Request-ID $request_id;
    }

    location /v1/payments/ {
        # Extra rate limiting for payment endpoints
        limit_req zone=api burst=5 nodelay;
        proxy_pass http://payment_service/;
    }
}

This handles routing, rate limiting, request ID propagation, and SSL termination with zero additional dependencies.

Kong: The Extensible Gateway for Complex Needs

Kong is the most widely deployed open-source API gateway. Built on Nginx/OpenResty, it adds a plugin system, admin API, and declarative configuration on top.

# Kong declarative config (kong.yml) — deploy with Kong in DB-less mode
_format_version: "3.0"

services:
  - name: user-service
    url: http://user-service:8001
    plugins:
      - name: jwt
        config:
          key_claim_name: kid
          secret_is_base64: false
      - name: rate-limiting
        config:
          minute: 1000
          hour: 10000
          policy: local
    routes:
      - name: user-routes
        paths:
          - /v1/users
        strip_path: false

  - name: payment-service
    url: http://payment-service:8003
    plugins:
      - name: jwt
      - name: rate-limiting
        config:
          minute: 100   # Stricter for payments
          policy: redis  # Distributed rate limiting via Redis
          redis_host: redis
    routes:
      - name: payment-routes
        paths:
          - /v1/payments

plugins:
  - name: correlation-id     # Add X-Correlation-ID to all requests
    config:
      header_name: X-Request-ID
      generator: uuid
  - name: prometheus          # Expose /metrics endpoint
  - name: file-log
    config:
      path: /dev/stdout
      reopen: false
# docker-compose.yml for Kong in DB-less mode
services:
  kong:
    image: kong:3.6
    environment:
      KONG_DATABASE: "off"
      KONG_DECLARATIVE_CONFIG: /kong/declarative/kong.yml
      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:
      - "80:8000"
      - "443:8443"
      - "8001:8001"   # Admin API — do NOT expose publicly
    volumes:
      - ./kong.yml:/kong/declarative/kong.yml

When Kong Makes Sense

  • Developer portal for external API consumers
  • Complex authentication flows (OAuth, multiple JWT issuers)
  • Per-consumer rate limiting (different limits for different API key tiers)
  • Rich plugin ecosystem (LDAP, AWS Lambda, GraphQL, canary deploys)
  • Teams that need a GUI admin interface

Envoy: The Proxy for Service Meshes

Envoy is a Layer 7 proxy originally built at Lyft. It’s the data plane for Istio and the core of most Kubernetes service meshes. Unlike Kong (which is designed as an ingress/edge gateway), Envoy is designed to run as a sidecar proxy next to every service instance.

# envoy.yaml — front proxy configuration
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 10000
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                access_log:
                  - name: envoy.access_loggers.stdout
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
                http_filters:
                  - name: envoy.filters.http.jwt_authn
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
                      providers:
                        myapp_jwt:
                          issuer: "https://auth.example.com"
                          remote_jwks:
                            http_uri:
                              uri: "https://auth.example.com/.well-known/jwks.json"
                              cluster: auth_cluster
                              timeout: 5s
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                route_config:
                  name: local_route
                  virtual_hosts:
                    - name: services
                      domains: ["*"]
                      routes:
                        - match:
                            prefix: "/v1/users"
                          route:
                            cluster: user_service
                        - match:
                            prefix: "/v1/orders"
                          route:
                            cluster: order_service

When Envoy Makes Sense

  • Running in Kubernetes with a service mesh requirement
  • Need advanced traffic management (circuit breaking, retries, outlier detection built in)
  • gRPC-first architecture (Envoy has first-class gRPC support)
  • Platform teams building internal infrastructure products

When Envoy Is Overkill

Envoy’s configuration is notoriously verbose and complex. Running Istio on a 3-service application adds significant operational overhead — Envoy sidecars to every pod, control plane components, certificate management. Many teams adopt it for Kubernetes “best practices” compliance and spend more time debugging the service mesh than their actual application.

Traefik: The Developer-Friendly Middle Ground

Traefik is purpose-built for container environments. It auto-discovers Docker and Kubernetes services and configures itself based on labels/annotations — no manual routing configuration:

# docker-compose.yml with Traefik auto-discovery
services:
  traefik:
    image: traefik:v3.0
    command:
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"  # Dashboard
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

  user-service:
    image: myapp/user-service
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.users.rule=PathPrefix(`/v1/users`)"
      - "traefik.http.routers.users.entrypoints=websecure"
      - "traefik.http.routers.users.tls.certresolver=letsencrypt"
      - "traefik.http.middlewares.users-ratelimit.ratelimit.average=100"
      - "traefik.http.middlewares.users-ratelimit.ratelimit.burst=50"
      - "traefik.http.routers.users.middlewares=users-ratelimit"

The Decision Matrix

Matching your situation to the right tool:

  • Monolith or 2-3 services, no public API: Nginx or Caddy reverse proxy. No API gateway needed.
  • Docker Compose, small microservices, developer experience matters: Traefik. Auto-discovery is a genuine productivity win.
  • Public developer API, complex auth, per-tier rate limits: Kong. The plugin ecosystem and admin UI are worth it.
  • Kubernetes, service mesh, platform engineering team: Envoy/Istio or Cilium service mesh.
  • Managed cloud: AWS API Gateway, GCP API Gateway, or Cloudflare API Shield if you’re already on those platforms.

The key insight: an API gateway is infrastructure you have to operate, monitor, debug, and upgrade. Choose the simplest option that meets your actual requirements, not the one with the most features.

By Michael Sun

Founder and Editor-in-Chief of NovVista. Software engineer with hands-on experience in cloud infrastructure, full-stack development, and DevOps. Writes about AI tools, developer workflows, server architecture, and the practical side of technology. Based in China.

Leave a Reply

Your email address will not be published. Required fields are marked *