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@v2
        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.