๐ŸŽ‰ 75% of content is free forever โ€” Unlock Premium from $10/mo โ†’
CW
Search coursesโ€ฆ
๐Ÿ’ผ Servicesโ„น๏ธ Aboutโœ‰๏ธ ContactView Pricing Plansfrom $10

API Gateway Patterns

Cloud ArchitectureAPI Managementโญ Premium

Advertisement

API Gateway Patterns

Difficulty: Senior Level | Companies: AWS, Google, Microsoft, Netflix, Uber

Why API Gateway

API Gateway is the single entry point for all client requests. It handles cross-cutting concerns like authentication, rate limiting, and request routing.

โ„น๏ธ

API Gateway eliminates the need for clients to know internal service addresses. It also provides a place to enforce security policies uniformly.

Gateway Architecture

Architecture Diagram
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚           Clients               โ”‚
                    โ”‚   Mobile / Web / Third Party    โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                   โ”‚
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚         API Gateway             โ”‚
                    โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”‚
                    โ”‚  โ”‚  Authentication          โ”‚   โ”‚
                    โ”‚  โ”‚  Rate Limiting           โ”‚   โ”‚
                    โ”‚  โ”‚  Request Validation      โ”‚   โ”‚
                    โ”‚  โ”‚  Response Transformation โ”‚   โ”‚
                    โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                   โ”‚
              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
              โ”‚                    โ”‚                    โ”‚
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ผโ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚  User Service โ”‚   โ”‚ Order Service โ”‚   โ”‚Product Serviceโ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Pattern 1: Request/Response Transformation

Transform API shapes between external and internal formats.

// Gateway request transformation
export class RequestTransformer {
  transformIncoming(request: IncomingRequest): InternalRequest {
    // Flatten nested external format to internal format
    return {
      userId: request.user.sub,
      items: request.body.orderItems.map((item: any) => ({
        productId: item.id,
        quantity: item.qty,
        price: parseFloat(item.unitPrice),
      })),
      shippingAddress: {
        street: request.body.shipping.street,
        city: request.body.shipping.city,
        state: request.body.shipping.region,
        zip: request.body.shipping.postalCode,
        country: request.body.shipping.countryCode,
      },
      metadata: {
        source: request.headers['x-client-id'],
        correlationId: request.headers['x-correlation-id'],
      },
    };
  }

  transformOutgoing(response: InternalResponse): OutgoingResponse {
    // Enrich internal response for external consumption
    return {
      id: response.id,
      status: response.status,
      items: response.items.map((item: any) => ({
        id: item.productId,
        name: item.productName,
        quantity: item.quantity,
        unitPrice: item.price.toString(),
        subtotal: (item.quantity * item.price).toString(),
      })),
      total: response.total.toString(),
      shipping: {
        address: response.shippingAddress,
        estimatedDelivery: response.estimatedDelivery,
      },
      createdAt: response.createdAt.toISOString(),
    };
  }
}

// API Gateway middleware
app.use('/api/v1/*', async (req, res, next) => {
  try {
    // Transform request
    const transformer = new RequestTransformer();
    const internalRequest = transformer.transformIncoming(req);
    
    // Forward to internal service
    const internalResponse = await httpClient.post(
      `${INTERNAL_SERVICES_URL}/orders`,
      internalRequest,
      {
        headers: {
          'X-User-Id': req.user.sub,
          'X-Correlation-Id': req.headers['x-correlation-id'],
        },
      }
    );
    
    // Transform response
    const outgoingResponse = transformer.transformOutgoing(internalResponse.data);
    res.json(outgoingResponse);
  } catch (error) {
    next(error);
  }
});

Pattern 2: Rate Limiting with Redis

Implement multi-tier rate limiting.

# Redis-based rate limiter
import redis
import time
from dataclasses import dataclass
from typing import Optional

@dataclass
class RateLimitResult:
    allowed: bool
    remaining: int
    retry_after: Optional[float] = None

class MultiTierRateLimiter:
    def __init__(self, redis_client: redis.Redis):
        self.redis = redis_client
    
    async def check_rate_limit(
        self,
        identifier: str,
        tier: str = 'standard',
    ) -> RateLimitResult:
        limits = {
            'free': {'requests': 100, 'window': 60},
            'standard': {'requests': 1000, 'window': 60},
            'premium': {'requests': 10000, 'window': 60},
        }
        
        limit = limits.get(tier, limits['standard'])
        key = f"rate_limit:{identifier}:{tier}"
        
        pipe = self.redis.pipeline()
        now = time.time()
        window_start = now - limit['window']
        
        # Sliding window log algorithm
        pipe.zremrangebyscore(key, 0, window_start)
        pipe.zadd(key, {str(now): now})
        pipe.zcard(key)
        pipe.expire(key, limit['window'])
        
        results = pipe.execute()
        request_count = results[2]
        
        if request_count > limit['requests']:
            # Rate limited - calculate retry after
            oldest = self.redis.zrange(key, 0, 0, withscores=True)
            retry_after = oldest[0][1] + limit['window'] - now if oldest else limit['window']
            
            return RateLimitResult(
                allowed=False,
                remaining=0,
                retry_after=retry_after,
            )
        
        return RateLimitResult(
            allowed=True,
            remaining=limit['requests'] - request_count,
        )

โ„น๏ธ

Use sliding window algorithms over fixed windows to avoid burst issues at window boundaries.

Pattern 3: JWT Validation at Gateway

Validate tokens once at the gateway, not in every service.

// JWT validation middleware
import jwt from 'jsonwebtoken';
import jwksClient from 'jwks-rsa';

const client = jwksClient({
  jwksUri: 'https://auth.example.com/.well-known/jwks.json',
  cache: true,
  rateLimit: true,
  jwksRequestsPerMinute: 10,
});

async function getKey(header: jwt.JwtHeader): Promise<string> {
  return new Promise((resolve, reject) => {
    client.getSigningKey(header.kid, (err, key) => {
      if (err) return reject(err);
      resolve(key.getPublicKey());
    });
  });
}

export const validateJwt = async (req: Request, res: Response, next: NextFunction) => {
  const authHeader = req.headers.authorization;
  
  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing authorization token' });
  }
  
  const token = authHeader.substring(7);
  
  try {
    const decoded = await new Promise<any>((resolve, reject) => {
      jwt.verify(
        token,
        getKey,
        {
          audience: 'api://gateway',
          issuer: 'https://auth.example.com',
          algorithms: ['RS256'],
        },
        (err, payload) => {
          if (err) reject(err);
          else resolve(payload);
        }
      );
    });
    
    // Attach user info to request for downstream services
    req.user = {
      sub: decoded.sub,
      email: decoded.email,
      roles: decoded.roles || [],
      permissions: decoded.permissions || [],
    };
    
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired' });
    }
    return res.status(401).json({ error: 'Invalid token' });
  }
};

Pattern 4: Circuit Breaker for Backend Services

Protect against cascading failures.

// Circuit breaker implementation
enum CircuitState {
  CLOSED = 'CLOSED',
  OPEN = 'OPEN',
  HALF_OPEN = 'HALF_OPEN',
}

class CircuitBreaker {
  private state: CircuitState = CircuitState.CLOSED;
  private failureCount: number = 0;
  private lastFailureTime: number = 0;
  private successCount: number = 0;

  constructor(
    private failureThreshold: number = 5,
    private resetTimeout: number = 30000,
    private halfOpenMaxAttempts: number = 3,
  ) {}

  async execute<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === CircuitState.OPEN) {
      if (Date.now() - this.lastFailureTime > this.resetTimeout) {
        this.state = CircuitState.HALF_OPEN;
        this.successCount = 0;
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failureCount = 0;
    if (this.state === CircuitState.HALF_OPEN) {
      this.successCount++;
      if (this.successCount >= this.halfOpenMaxAttempts) {
        this.state = CircuitState.CLOSED;
      }
    }
  }

  private onFailure(): void {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.failureCount >= this.failureThreshold) {
      this.state = CircuitState.OPEN;
    }
  }
}

// Usage in gateway
const orderServiceBreaker = new CircuitBreaker(5, 30000);

app.post('/api/v1/orders', async (req, res) => {
  try {
    const result = await orderServiceBreaker.execute(() =>
      httpClient.post('http://order-service/orders', req.body)
    );
    res.json(result.data);
  } catch (error) {
    if (error.message === 'Circuit breaker is OPEN') {
      res.status(503).json({
        error: 'Service temporarily unavailable',
        retryAfter: 30,
      });
    } else {
      res.status(500).json({ error: 'Internal server error' });
    }
  }
});

Pattern 5: API Composition at Gateway

Aggregate responses from multiple services.

// Gateway composition endpoint
export class ComposedEndpoints {
  constructor(
    private userService: UserServiceClient,
    private orderService: OrderServiceClient,
    private inventoryService: InventoryServiceClient,
  ) {}

  async getUserDashboard(userId: string): Promise<UserDashboard> {
    // Fetch all data in parallel
    const [user, orders, recommendations] = await Promise.all([
      this.userService.getUser(userId),
      this.orderService.getUserOrders(userId, { limit: 10 }),
      this.inventoryService.getRecommendations(userId),
    ]);

    // Compose response
    return {
      user: {
        id: user.id,
        name: user.name,
        email: user.email,
      },
      recentOrders: orders.map(order => ({
        id: order.id,
        status: order.status,
        total: order.total,
        itemCount: order.items.length,
      })),
      recommendations: recommendations.slice(0, 5),
      stats: {
        totalOrders: orders.length,
        totalSpent: orders.reduce((sum, o) => sum + o.total, 0),
      },
    };
  }
}

โš ๏ธ

Gateway composition adds latency (slowest service determines response time). Use it for BFF (Backend for Frontend) patterns where clients need aggregated data.

Gateway Pattern Summary

PatternUse CaseComplexity
Request TransformationAPI versioningLow
Rate LimitingAbuse preventionMedium
JWT ValidationCentralized authMedium
Circuit BreakerResilienceMedium
API CompositionBFF patternHigh

Follow-Up Questions

  1. How do you handle API versioning in a gateway without breaking existing clients?
  2. What strategies would you use to deploy gateway changes without downtime?
  3. How do you implement request/response logging for debugging without impacting performance?

Advertisement