API Gateway Example
Learn how to test an API gateway with authentication, rate limiting, request transformation, and advanced routing features.
Overview
This example showcases an API gateway that provides:
- Authentication validation: API key checking
- Rate limiting simulation: Headers indicating rate limit status
- Request transformation: Adding metadata headers
- Routing: Path-based routing to backend services
- Access control: Public vs protected vs admin endpoints
Use Case
Perfect for:
- API gateway testing
- Authentication and authorization validation
- Rate limiting verification
- Request/response transformation testing
- Security policy validation
- API versioning strategy validation
Architecture
Client → Nginx (API Gateway) → Backend Services
├── Authentication
├── Rate Limiting
├── Request Transformation
└── RoutingGateway Features
Authentication Tiers
- Public: No authentication required
- Standard API: Requires valid API key
- Admin API: Requires admin API key
Rate Limiting
- Public endpoints: 1000 requests/hour
- Standard API: 100 requests/hour
- Admin API: 10000 requests/hour
Request Transformation
- Gateway metadata injection
- Request ID generation
- Authentication status headers
- User tier identification
File Structure
api-gateway/
├── Dockerfile
├── nginx.conf
└── .httptests/
├── config.yml
└── test.jsonConfiguration Files
Dockerfile
dockerfile
FROM nginx:alpine
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]nginx.conf
nginx
events {
worker_connections 1024;
}
http {
# Map for API key validation
map $http_x_api_key $auth_status {
default "unauthorized";
"valid-api-key-12345" "authenticated";
"admin-key-67890" "authenticated";
}
map $http_x_api_key $user_tier {
default "none";
"valid-api-key-12345" "standard";
"admin-key-67890" "admin";
}
map $http_x_api_key $rate_limit {
default "1000";
"valid-api-key-12345" "100";
"admin-key-67890" "10000";
}
server {
listen 80;
server_name _;
# Health check
location = /health {
default_type application/json;
add_header X-Gateway-Version "1.0.0";
return 200 '{"status": "healthy", "gateway": "api-gateway"}';
}
# Public endpoints (no auth required)
location /public/ {
proxy_pass http://backend:80/;
# Add gateway headers
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Gateway-Route "public";
proxy_set_header X-Auth-Status "not-required";
# Rate limiting headers (simulated)
add_header X-RateLimit-Limit "1000";
add_header X-RateLimit-Remaining "999";
proxy_pass_request_headers on;
}
# Protected API endpoints
location /api/ {
# In real scenario, would check auth and return 401
# For testing, we simulate authenticated requests
proxy_pass http://backend:80/;
# Add gateway and auth headers
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Gateway-Route "api";
proxy_set_header X-Auth-Status $auth_status;
proxy_set_header X-User-Tier $user_tier;
# Rate limiting headers based on tier
add_header X-RateLimit-Limit $rate_limit;
add_header X-RateLimit-Remaining "99" if ($user_tier = "standard");
add_header X-RateLimit-Remaining "9999" if ($user_tier = "admin");
proxy_pass_request_headers on;
}
# Admin endpoints
location /admin/ {
proxy_pass http://backend:80/;
# Add gateway and auth headers
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Gateway-Route "admin";
proxy_set_header X-Auth-Status $auth_status;
proxy_set_header X-User-Tier $user_tier;
# Admin rate limits
add_header X-RateLimit-Limit "10000";
add_header X-RateLimit-Remaining "9999";
proxy_pass_request_headers on;
}
}
}Key features:
- Maps for API key validation and user tier detection
- Different rate limits per tier
- Route-specific header injection
- Authentication status propagation
.httptests/config.yml
yaml
mock:
network_aliases:
- backend
nginx:
environment:
GATEWAY_ENV: production
LOG_LEVEL: info.httptests/test.json
json
{
"collectionHeaders": [
["X-Request-ID"]
],
"hosts": {
"gateway.example.com": [
{
"paths": ["/health"],
"method": "GET",
"expectedStatus": 200,
"expectedResponseHeaders": [
["Content-Type", "application/json"],
["X-Gateway-Version", "1.0.0"]
]
},
{
"paths": ["/public/info"],
"method": "GET",
"expectedStatus": 200,
"expectedResponseHeaders": [
["X-RateLimit-Limit", "1000"],
["X-RateLimit-Remaining", "999"]
],
"expectedRequestHeadersToUpstream": [
["$collectionHeaders"],
["X-Forwarded-For"],
["X-Real-IP"],
["X-Gateway-Route", "public"],
["X-Auth-Status", "not-required"]
]
},
{
"paths": ["/api/users"],
"method": "GET",
"additionalRequestHeaders": {
"X-API-Key": "valid-api-key-12345"
},
"expectedStatus": 200,
"expectedResponseHeaders": [
["X-RateLimit-Limit", "100"],
["X-RateLimit-Remaining", "99"]
],
"expectedRequestHeadersToUpstream": [
["$collectionHeaders"],
["X-Forwarded-For"],
["X-Real-IP"],
["X-Gateway-Route", "api"],
["X-Auth-Status", "authenticated"],
["X-User-Tier", "standard"]
]
},
{
"paths": ["/admin/settings"],
"method": "GET",
"additionalRequestHeaders": {
"X-API-Key": "admin-key-67890"
},
"expectedStatus": 200,
"expectedResponseHeaders": [
["X-RateLimit-Limit", "10000"],
["X-RateLimit-Remaining", "9999"]
],
"expectedRequestHeadersToUpstream": [
["$collectionHeaders"],
["X-Forwarded-For"],
["X-Real-IP"],
["X-Gateway-Route", "admin"],
["X-Auth-Status", "authenticated"],
["X-User-Tier", "admin"]
]
}
]
}
}Test Coverage
The test suite validates:
- ✅ Public endpoint access without authentication
- ✅ Protected endpoint authentication headers
- ✅ Admin endpoint special permissions
- ✅ Rate limiting headers per tier
- ✅ Request transformation (added headers)
- ✅ Gateway metadata injection
- ✅ Proper routing based on authentication
GitHub Actions Workflow
yaml
name: API Gateway Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: serviceguards-com/httptests-action@v2
with:
httptests-directory: ./api-gatewayExpected Test Output
🔧 Adding X-Upstream-Target headers to nginx configs...
🚀 Starting environment...
🧪 Running tests for httptests-api-gateway
test_endpoints (__main__.IntegrationTests.test_endpoints) ... ok
→ Testing: GET gateway.example.com/health
✓ Status code: 200 (expected 200)
✓ Response header: content-type = application/json
✓ Response header: x-gateway-version = 1.0.0
→ Testing: GET gateway.example.com/public/info
✓ Status code: 200 (expected 200)
✓ Response header: x-ratelimit-limit = 1000
✓ Response header: x-ratelimit-remaining = 999
✓ Request header: x-gateway-route = public
✓ Request header: x-auth-status = not-required
→ Testing: GET gateway.example.com/api/users
✓ Status code: 200 (expected 200)
✓ Response header: x-ratelimit-limit = 100
✓ Response header: x-ratelimit-remaining = 99
✓ Request header: x-auth-status = authenticated
✓ Request header: x-user-tier = standard
→ Testing: GET gateway.example.com/admin/settings
✓ Status code: 200 (expected 200)
✓ Response header: x-ratelimit-limit = 10000
✓ Request header: x-user-tier = admin
============================================================
Total assertions passed: 16
============================================================Key Testing Patterns
Tiered Authentication
Tests verify different authentication tiers:
json
{
"paths": ["/public/info"],
"method": "GET",
"expectedRequestHeadersToUpstream": [
["X-Auth-Status", "not-required"]
]
},
{
"paths": ["/api/users"],
"additionalRequestHeaders": {
"X-API-Key": "valid-api-key-12345"
},
"expectedRequestHeadersToUpstream": [
["X-Auth-Status", "authenticated"],
["X-User-Tier", "standard"]
]
},
{
"paths": ["/admin/settings"],
"additionalRequestHeaders": {
"X-API-Key": "admin-key-67890"
},
"expectedRequestHeadersToUpstream": [
["X-User-Tier", "admin"]
]
}Rate Limit Validation
Different rate limits for different tiers:
json
{
"paths": ["/public/info"],
"expectedResponseHeaders": [
["X-RateLimit-Limit", "1000"]
]
},
{
"paths": ["/api/users"],
"expectedResponseHeaders": [
["X-RateLimit-Limit", "100"]
]
},
{
"paths": ["/admin/settings"],
"expectedResponseHeaders": [
["X-RateLimit-Limit", "10000"]
]
}Route Identification
Gateway adds route metadata:
json
{
"paths": ["/public/info"],
"expectedRequestHeadersToUpstream": [
["X-Gateway-Route", "public"]
]
}Extending This Example
Add JWT Authentication
nginx
# Decode and validate JWT
location /api/ {
auth_jwt "API Gateway";
auth_jwt_key_file /etc/nginx/jwt-key.json;
proxy_pass http://backend:80/;
proxy_set_header X-User-ID $jwt_claim_sub;
proxy_set_header X-User-Email $jwt_claim_email;
}Add OAuth2 Integration
nginx
location /api/ {
# OAuth2 proxy integration
auth_request /oauth2/auth;
auth_request_set $user $upstream_http_x_auth_request_user;
proxy_pass http://backend:80/;
proxy_set_header X-User $user;
}Add Request Body Size Limits
nginx
location /api/upload {
client_max_body_size 10M;
proxy_pass http://backend:80/;
}Add CORS Handling
nginx
location /api/ {
# CORS headers
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-API-Key";
if ($request_method = OPTIONS) {
return 204;
}
proxy_pass http://backend:80/;
}Test CORS:
json
{
"paths": ["/api/users"],
"method": "OPTIONS",
"expectedStatus": 204,
"expectedResponseHeaders": [
["Access-Control-Allow-Origin", "*"],
["Access-Control-Allow-Methods"],
["Access-Control-Allow-Headers"]
]
}Real-World Gateway Patterns
API Versioning
nginx
location /v1/api/ {
proxy_pass http://backend-v1:80/;
proxy_set_header X-API-Version "v1";
}
location /v2/api/ {
proxy_pass http://backend-v2:80/;
proxy_set_header X-API-Version "v2";
}Request ID Generation
nginx
map $http_x_request_id $request_id {
default $http_x_request_id;
"" $request_id;
}
location /api/ {
proxy_set_header X-Request-ID $request_id;
proxy_pass http://backend:80/;
}IP Allowlisting
nginx
geo $allowed {
default 0;
10.0.0.0/8 1;
172.16.0.0/12 1;
192.168.0.0/16 1;
}
location /admin/ {
if ($allowed = 0) {
return 403;
}
proxy_pass http://backend:80/;
}Related Examples
- Microservices - Multi-service architecture
- Nginx Proxy - Basic proxy configuration
- API Testing - REST API testing
Next Steps
- Learn about Collection Headers for reducing duplication
- Explore test.json configuration in detail
- Understand Upstream Tracking