| name | api-gateway |
| description | Design and configure API gateway architecture covering routing, authentication, rate limiting, request transformation, and observability. Outputs gateway configuration for Kong, AWS API Gateway, or Nginx, with traffic management and security policies. |
| argument-hint | ["gateway technology","auth method","rate limiting requirements","upstream services"] |
| allowed-tools | Read, Write, Bash |
API Gateway
An API gateway is the front door to your microservices. It centralizes cross-cutting concerns — auth, rate limiting, logging, routing — so each service doesn't have to implement them. A well-configured gateway dramatically simplifies service development while improving security and observability.
When to Use an API Gateway
| Situation | Recommendation |
|---|
| Single service, simple needs | Skip — use Nginx reverse proxy |
| Multiple services, shared auth | API gateway is the right tool |
| Public API with third-party consumers | Essential |
| Internal service mesh | Consider service mesh instead |
| Multi-cloud, multi-region | Gateway + service mesh together |
Process
- Map upstream services — what services need to be exposed and to whom.
- Define authentication strategy — JWT, API key, OAuth, mTLS, or combinations.
- Configure routing — path-based, header-based, or hostname-based routing.
- Set rate limits — per consumer, per endpoint, global limits.
- Add request/response transforms — header injection, payload transformation.
- Configure observability — access logs, metrics, distributed tracing.
- Test end-to-end — including auth failures, rate limit behavior, and failover.
Output Format
Kong Gateway Configuration
_format_version: "3.0"
services:
- name: order-service
url: http://order-service.production.svc.cluster.local:8080
connect_timeout: 5000
write_timeout: 10000
read_timeout: 10000
retries: 2
tags: [production, orders]
routes:
- name: orders-v1
paths: ["/api/v1/orders"]
methods: [GET, POST, PUT, PATCH, DELETE]
strip_path: false
preserve_host: false
tags: [production]
plugins:
- name: jwt
config:
key_claim_name: sub
claims_to_verify: [exp, nbf]
maximum_expiration: 3600
run_on_preflight: false
[, ]
[, , , , , ]
[, , ]
[, ]
[]
[, , ]
[]
[]
[]
[]
[, ]
[, ]
[, ]
[]
AWS API Gateway (CDK)
import * as cdk from 'aws-cdk-lib';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as wafv2 from 'aws-cdk-lib/aws-wafv2';
import { Construct } from 'constructs';
export class ApiGatewayStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const userPool = cognito.UserPool.fromUserPoolId(
this, 'UserPool', process.env.USER_POOL_ID!
);
const webAcl = new wafv2.(, , {
: ,
: ,
: { : {} },
: [
{
: ,
: ,
: { : {} },
: {
: {
: ,
: ,
},
},
: {
: ,
: ,
: ,
},
},
{
: ,
: ,
: { : {} },
: {
: {
: ,
: ,
},
},
: {
: ,
: ,
: ,
},
},
],
: {
: ,
: ,
: ,
},
});
api = apigateway.(, , {
: ,
: ,
: {
: ,
: ,
: ,
: ,
: apigateway..,
: ,
: apigateway.(
cdk..(, , {
: cdk...,
})
),
: apigateway..({
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
: ,
}),
},
: {
: [],
: [, , , , ],
: [, , ],
: cdk..(),
},
});
authorizer = apigateway.(, , {
: [userPool],
: ,
: ,
});
orderHandler = lambda..(
, , process..!
);
usagePlan = api.(, {
: ,
: {
: ,
: ,
},
: {
: ,
: apigateway..,
},
});
apiKey = api.(, {
: ,
: ,
});
usagePlan.(apiKey);
usagePlan.({ : api. });
ordersResource = api..();
ordersResource.(, apigateway.(orderHandler), {
authorizer,
: apigateway..,
: apigateway.(, , {
: api,
: ,
}),
: {
: ,
: ,
},
});
ordersResource.(, apigateway.(orderHandler), {
authorizer,
: apigateway..,
: apigateway.(, , {
: api,
: ,
}),
: {
: apigateway.(, , {
: api,
: ,
: {
: apigateway..,
: [],
: {
: {
: apigateway..,
: {
: apigateway..,
: [, ],
: {
: { : apigateway.. },
: { : apigateway.., : },
},
},
},
},
},
}),
},
});
wafv2.(, , {
: api..,
: webAcl.,
});
cdk.(, , { : api. });
cdk.(, , { : apiKey. });
}
}
Nginx Gateway (Simpler Alternative)
# /etc/nginx/conf.d/api-gateway.conf
upstream order_service {
least_conn;
server order-service-1:8080 weight=1 max_fails=3 fail_timeout=30s;
server order-service-2:8080 weight=1 max_fails=3 fail_timeout=30s;
server order-service-3:8080 weight=1 max_fails=3 fail_timeout=30s;
keepalive 32;
}
upstream user_service {
least_conn;
server user-service-1:8080;
server user-service-2:8080;
keepalive 16;
}
# Rate limit zones
limit_req_zone $binary_remote_addr zone=api_global:10m rate=100r/m;
limit_req_zone $http_x_api_key zone=api_key:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=auth:10m rate=10r/m;
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/api.example.com.crt;
ssl_certificate_key /etc/ssl/private/api.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
# Remove server version
server_tokens off;
# Request ID for tracing
add_header X-Request-ID $request_id always;
# Global rate limit
limit_req zone=api_global burst=20 nodelay;
limit_req_status 429;
# JWT validation via auth_request
location = /auth/validate {
internal;
proxy_pass http://auth-service:8080/validate;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Method $request_method;
proxy_set_header Authorization $http_authorization;
}
# Orders API
location /api/v1/orders {
# JWT validation
auth_request /auth/validate;
auth_request_set $auth_user_id $upstream_http_x_user_id;
# Rate limiting
limit_req zone=api_global burst=10 nodelay;
# Upstream proxy
proxy_pass http://order_service;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-User-ID $auth_user_id;
proxy_set_header X-Request-ID $request_id;
# Don't forward auth header to upstream
proxy_set_header Authorization "";
# Timeouts
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 10s;
}
# Auth endpoints — stricter rate limit, no JWT
location /api/v1/auth {
limit_req zone=auth burst=5 nodelay;
proxy_pass http://user_service;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Request-ID $request_id;
}
# Health check endpoint (no auth, no rate limit)
location /health {
access_log off;
return 200 '{"status":"ok"}';
add_header Content-Type application/json;
}
# Default: 404
location / {
return 404 '{"error":"Not Found"}';
add_header Content-Type application/json;
}
# Access log with timing
log_format api_json escape=json
'{'
'"time":"$time_iso8601",'
'"method":"$request_method",'
'"uri":"$uri",'
'"status":$status,'
'"bytes_sent":$bytes_sent,'
'"request_time":$request_time,'
'"upstream_time":"$upstream_response_time",'
'"request_id":"$request_id",'
'"remote_addr":"$remote_addr",'
'"user_agent":"$http_user_agent"'
'}';
access_log /var/log/nginx/api-access.log api_json;
error_log /var/log/nginx/api-error.log warn;
}
# HTTP → HTTPS redirect
server {
listen 80;
server_name api.example.com;
return 301 https://$server_name$request_uri;
}
Gateway Observability
"""Parse gateway access logs and push metrics to Prometheus."""
import json
import time
from prometheus_client import Counter, Histogram, start_http_server
request_count = Counter(
'gateway_requests_total',
'Total requests through gateway',
['method', 'endpoint', 'status', 'upstream']
)
request_duration = Histogram(
'gateway_request_duration_seconds',
'Request duration through gateway',
['method', 'endpoint', 'upstream'],
buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
)
def process_log_line(line: str):
try:
entry = json.loads(line)
endpoint = entry.get('uri', 'unknown').split('?')[0]
endpoint = normalize_endpoint(endpoint)
request_count.labels(
method=entry.get('method', 'unknown'),
endpoint=endpoint,
status=str(entry.get('status', 0)),
upstream=entry.get('upstream', 'unknown'),
).inc()
duration = float(entry.get(, ))
request_duration.labels(
method=entry.get(, ),
endpoint=endpoint,
upstream=entry.get(, ),
).observe(duration)
(json.JSONDecodeError, ValueError):
() -> :
re
path = re.sub(, , path)
path = re.sub(, , path)
path
Rules
- Centralize auth at the gateway — don't implement JWT validation in every service.
- Don't pass raw credentials upstream — gateway validates auth, then injects user context via headers.
- Rate limit at the gateway, not in services — centralized enforcement is consistent and observable.
- Request IDs are mandatory — every request needs a traceable ID injected at the gateway.
- Timeout at every layer — gateway timeout, upstream timeout, and circuit breaker must all be configured.
- Log structured access logs — JSON format enables analytics; include method, path, status, duration, user.
- Validate request schemas — reject malformed requests at the gateway before they hit services.
- WAF for public APIs — OWASP managed rule sets block common attack patterns with minimal configuration.
- Health check endpoints bypass auth — monitoring must reach health checks without credentials.
- Never log request bodies by default — they contain PII and credentials; enable only for specific debugging.