An API Gateway doesn’t just sit there; it actively shapes how your services are consumed, turning chaos into order.
Let’s imagine we’re building a system that exposes a few microservices: a user-service for profile data, an order-service for purchases, and a payment-service for handling transactions.
Here’s a simplified Nginx configuration acting as our API Gateway:
http {
upstream user_service {
server 192.168.1.10:8080;
server 192.168.1.11:8080;
}
upstream order_service {
server 192.168.1.20:8081;
}
upstream payment_service {
server 192.168.1.30:8082;
}
server {
listen 80;
server_name api.example.com;
location /users/ {
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-Forwarded-Proto $scheme;
}
location /orders/ {
proxy_pass http://order_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-Forwarded-Proto $scheme;
# Rate Limiting: 100 requests per minute
limit_req zone=api_per_minute burst=100 nodelay;
}
location /payments/ {
proxy_pass http://payment_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-Forwarded-Proto $scheme;
# Basic Authentication
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/htpasswd/payments.htpasswd;
}
}
}
http {
# Rate Limiting configuration
limit_req_zone api_per_minute zone=api_per_minute:10m rate=100r/m;
}
This Nginx config sets up three upstream groups, mapping specific URL paths (/users/, /orders/, /payments/) to these backend services. The proxy_pass directive is the core routing mechanism. proxy_set_header directives are crucial for passing along important client information to the backend services, which they might otherwise lose when proxied.
The user_service upstream is configured with two servers (192.168.1.10:8080 and 192.168.1.11:8080), enabling basic load balancing. Nginx will distribute requests between these two instances using a round-robin strategy by default. If one user-service instance goes down, Nginx will automatically stop sending traffic to it.
The order_service has a rate limit applied. limit_req_zone api_per_minute zone=api_per_minute:10m rate=100r/m; defines a zone named api_per_minute that allows 100 requests per minute (per IP by default, though this can be adjusted). limit_req zone=api_per_minute burst=100 nodelay; in the location block enforces this. If a client exceeds this limit, they’ll receive a 503 Service Temporarily Unavailable error.
The payment_service has basic authentication. auth_basic "Restricted Area"; prompts the client for credentials, and auth_basic_user_file /etc/nginx/htpasswd/payments.htpasswd; specifies the file containing usernames and hashed passwords (e.g., created with htpasswd -c /etc/nginx/htpasswd/payments.htpasswd username). Only clients providing valid credentials will be allowed to reach the payment_service.
The gateway handles these concerns so your individual services don’t have to. Your user-service just needs to know how to serve user data, not how to authenticate users or manage request rates. The order-service focuses on order logic, not on being hammered by bots. The payment-service needs to be secure, but it doesn’t need to know about the global rate limits for all APIs.
The real magic of an API Gateway is its ability to abstract away cross-cutting concerns. Think of it as a bouncer, a traffic cop, and a translator all rolled into one. It intercepts every incoming request and decides where it should go, who it’s for, and whether it’s behaving itself. This allows your backend services to be lean, focused, and simpler to develop and maintain.
What most people don’t realize is how much state the gateway can maintain and use for its decisions. The limit_req_zone directive, for instance, is stateful. It keeps track of request counts for each client IP over a defined time window. This state allows it to enforce rules dynamically, rather than just being a static router. You can imagine more complex setups where the gateway consults an external cache (like Redis) to track API keys, user session validity, or even dynamic rate limits based on user tier, all without touching the backend services.
Once you’ve got routing, authentication, and rate limiting handled, the next logical step is implementing robust request/response transformation and centralized logging.