Skip to content

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
         └── Routing

Gateway 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.json

Configuration 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@latest
        with:
          httptests-directory: ./api-gateway

Expected 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/;
}

Next Steps ​

Released under the MIT License.