The API Gateway Problem
You have 10 microservices. Clients need to call multiple services to complete one action.
Without a gateway, clients couple to all 10 services. Each service needs authentication, rate limiting, and logging. Services can't change without breaking clients.
An API gateway centralizes these concerns. One entry point. Clients call the gateway. Gateway routes to services.
What an API Gateway Does
- Routing: Forward /api/users → user-service, /api/orders → order-service
- Authentication: Validate JWT, OAuth tokens before forwarding to services
- Rate limiting: Allow 100 requests/minute per API key, reject excess
- Logging and tracing: Log all requests, add trace IDs for debugging
- Request transformation: Rewrite paths, headers, payloads
- Response aggregation: Combine responses from multiple services
- Caching: Cache GET responses
- Load balancing: Distribute traffic across multiple service instances
API Gateway Technologies
Kong:
- Open-source, self-hosted
- Extensive plugin ecosystem
- Strong for microservices architectures
- Cost: infrastructure + ops time
AWS API Gateway:
- Managed SaaS
- Integrates with AWS Lambda, RDS, etc.
- Pay per request ($3.50 per million requests)
- Limited customization
nginx:
- Lightweight, fast
- Powerful routing and transformation
- Manual configuration (less "magic")
- Best for: teams comfortable with configuration
GraphQL layer (Apollo, Hasura):
- Compose microservices into a single GraphQL API
- Clients request only needed data
- Solves N+1 queries and over-fetching
- Powerful but requires GraphQL expertise
API Gateway Routing
Path-based routing:
location /api/users/ {
proxy_pass http://user-service:3000;
}
location /api/orders/ {
proxy_pass http://order-service:3000;
}
Host-based routing:
upstream users_backend { server user-service:3000; }
upstream orders_backend { server order-service:3000; }
server {
server_name users.api.myapp.com;
location / {
proxy_pass http://users_backend;
}
}
Weighted routing (canary deployments):
Route 90% of traffic to v1, 10% to v2
Track error rate and latency for v2
Gradually increase to v2 as confidence grows
Authentication at the Gateway
Centralize auth. Services don't authenticate; gateway does.
Client sends request with JWT token
↓
Gateway verifies JWT signature
↓
If valid, add user info to headers
↓
Forward to backend service with X-User-ID, X-User-Roles headers
↓
Service trusts headers (gateway already verified)
OAuth 2.0 flow at gateway:
Client redirects to gateway /login
↓
Gateway redirects to OAuth provider (Google, GitHub)
↓
User authenticates with provider
↓
Provider redirects back to gateway with auth code
↓
Gateway exchanges code for access token
↓
Gateway issues session cookie, redirects to app
↓
Client cookies already has auth, calls services
↓
Gateway validates cookie, forwards to services
Rate Limiting
Prevent abuse and ensure fair usage.
Per API key: 1000 requests/hour
Per user: 100 requests/minute
Per IP: 1000 requests/minute (DDoS protection)
Algorithms:
- Token bucket: clients get tokens, spending a token per request. Refill over time.
- Sliding window: count requests in last minute. Block if > limit.
- Fixed window: reset count each minute. Simple but can burst at boundaries.
Implementation:
GET /api/data
Rate limit: 100 requests/minute
User has made 95 requests this minute
→ Allow request
→ Return header: X-RateLimit-Remaining: 4
User has made 100 requests
→ Reject with 429 Too Many Requests
Request/Response Transformation
Gateway can modify requests and responses.
Request transformation:
- Add headers (tracing IDs, auth tokens for internal calls)
- Rewrite paths (/v1/users → /users, backwards compatible)
- Transform JSON (API v1 format → v2 format)
Response transformation:
- Add headers (caching instructions, security headers)
- Transform JSON (add metadata, remove internal fields)
Example:
Client calls: GET /api/v1/users/123
Gateway rewrites to: GET /api/users/123 (internal v2 API)
User service responds: {"id": 123, "name": "Alice", "internal_id": "usr_123"}
Gateway transforms: {"userId": 123, "name": "Alice"} (removes internal_id)
GraphQL Composition
Combine multiple REST APIs into a single GraphQL interface.
Before:
GET /api/users/123 (get user)
GET /api/users/123/orders (get user's orders)
GET /api/products/456 (get product details)
Three requests, over-fetching data.
After (GraphQL):
query {
user(id: 123) {
name
orders {
id
total
product {
name
}
}
}
}
One request, only requested fields.
Tools: Apollo Server, Hasura, StepZen.
Downside: Adds complexity. Use only if clients have complex data needs.
API Gateway Checklist
- Centralized routing (one entry point for all services)
- Authentication and authorization
- Rate limiting per API key/user/IP
- Logging and distributed tracing
- Request/response transformation
- Load balancing across service instances
- Caching for GET requests
- API versioning (v1, v2)
- Monitoring (request rate, error rate, latency)
- Documentation (API docs, rate limit info)
Frequently asked questions
Should every microservice have its own API gateway?
No. One central gateway for external clients, internal routing via service mesh (Istio). A gateway per service adds complexity without benefit.
Can API gateway be a bottleneck?
Yes, if not designed right. Use auto-scaling, load balancing, and caching. Monitor gateway latency (should be <10ms overhead). If gateway adds >50ms latency, something is wrong.
GraphQL or REST at the gateway?
REST is simpler and standard. GraphQL is powerful for complex queries but adds operational complexity. Start with REST. Add GraphQL later if clients request flexible queries.