Microservices

API Gateway Patterns for SaaS Applications

API Gateway Patterns For SaaS Applications

Introduction

In a Software-as-a-Service (SaaS) environment, multiple services often power different parts of the platform—user accounts, billing, analytics, feature flags, and more. As these services multiply and tenants scale into the thousands, managing API traffic, authentication, and routing becomes increasingly complex. An API gateway acts as a single entry point for all client requests, providing a unified layer for routing, security, rate limiting, and observability. Companies like Stripe, Twilio, and Shopify rely heavily on sophisticated API gateway patterns to serve millions of API requests per minute while maintaining tenant isolation and consistent policies. In this comprehensive guide, we’ll explore API gateway patterns that make SaaS systems secure, maintainable, and scalable, with practical implementation examples.

What Is an API Gateway?

An API gateway is a layer that sits between clients (web apps, mobile apps, third-party integrations) and your backend services. Instead of clients calling each service directly, all requests flow through the gateway.

Core responsibilities:

Request routing: Direct traffic to appropriate backend services based on path, headers, or tenant.

Authentication: Validate API keys, JWTs, or OAuth tokens before requests reach services.

Rate limiting: Protect services from abuse with per-tenant or per-endpoint limits.

Request transformation: Modify headers, aggregate responses, or translate protocols.

Observability: Centralized logging, metrics, and distributed tracing.

Why API Gateways Matter for SaaS

SaaS platforms face unique challenges that make API gateways essential:

Multi-tenancy: Hundreds or thousands of tenants share infrastructure. The gateway enforces isolation through authentication and rate limits.

Consistent policies: Apply security, logging, and rate limiting uniformly across all APIs without duplicating logic in each service.

API versioning: Manage multiple API versions simultaneously while migrating tenants gradually.

Custom domains: Route api.tenant1.com and api.tenant2.com to the same infrastructure with tenant-specific configuration.

Usage metering: Track API calls per tenant for billing, analytics, and capacity planning.

API Gateway Patterns for SaaS

1. Single Gateway Pattern

All client requests flow through one centralized gateway that handles routing to all backend services.

# Kong Gateway configuration example
# kong.yml
_format_version: "3.0"

services:
  - name: user-service
    url: http://user-service:3000
    routes:
      - name: user-routes
        paths:
          - /api/v1/users
        strip_path: false
    plugins:
      - name: rate-limiting
        config:
          minute: 100
          policy: local

  - name: billing-service
    url: http://billing-service:3000
    routes:
      - name: billing-routes
        paths:
          - /api/v1/billing
    plugins:
      - name: jwt
        config:
          secret_is_base64: false
      - name: rate-limiting
        config:
          minute: 50

  - name: analytics-service
    url: http://analytics-service:3000
    routes:
      - name: analytics-routes
        paths:
          - /api/v1/analytics

Best for: Simpler SaaS applications with fewer services and straightforward routing needs.

2. Tenant-Aware Gateway Pattern

The gateway extracts tenant information from tokens, headers, or subdomains and applies tenant-specific policies:

// Express middleware for tenant-aware routing
import express from 'express';
import jwt from 'jsonwebtoken';

interface TenantContext {
  tenantId: string;
  plan: 'free' | 'pro' | 'enterprise';
  rateLimits: {
    requestsPerMinute: number;
    requestsPerDay: number;
  };
}

const TENANT_CONFIGS: Record = {
  'tenant-123': {
    tenantId: 'tenant-123',
    plan: 'enterprise',
    rateLimits: { requestsPerMinute: 1000, requestsPerDay: 100000 },
  },
  'tenant-456': {
    tenantId: 'tenant-456',
    plan: 'pro',
    rateLimits: { requestsPerMinute: 100, requestsPerDay: 10000 },
  },
};

function extractTenantMiddleware(req: express.Request, res: express.Response, next: express.NextFunction) {
  // Extract tenant from JWT
  const token = req.headers.authorization?.replace('Bearer ', '');
  
  if (!token) {
    return res.status(401).json({ error: 'Missing authorization token' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET!) as { tenantId: string };
    const tenantConfig = TENANT_CONFIGS[decoded.tenantId];
    
    if (!tenantConfig) {
      return res.status(403).json({ error: 'Unknown tenant' });
    }

    // Attach tenant context to request
    req.tenant = tenantConfig;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// Rate limiting based on tenant plan
function tenantRateLimiter(req: express.Request, res: express.Response, next: express.NextFunction) {
  const tenant = req.tenant;
  const key = `ratelimit:${tenant.tenantId}`;
  
  // Check rate limit from Redis
  // Implementation depends on your rate limiting strategy
  
  // Add rate limit headers
  res.setHeader('X-RateLimit-Limit', tenant.rateLimits.requestsPerMinute);
  res.setHeader('X-RateLimit-Remaining', remaining);
  res.setHeader('X-RateLimit-Reset', resetTime);
  
  next();
}

const app = express();
app.use(extractTenantMiddleware);
app.use(tenantRateLimiter);

3. Backend-for-Frontend (BFF) Pattern

Create separate gateways optimized for different clients (web, mobile, third-party):

// BFF Gateway for mobile clients
import express from 'express';
import axios from 'axios';

const mobileBFF = express();

// Aggregate multiple service calls into one response
mobileBFF.get('/api/mobile/dashboard', async (req, res) => {
  try {
    // Parallel requests to multiple services
    const [userResponse, billingResponse, notificationsResponse] = await Promise.all([
      axios.get(`${USER_SERVICE}/users/${req.user.id}`, {
        headers: { Authorization: req.headers.authorization },
      }),
      axios.get(`${BILLING_SERVICE}/subscriptions/${req.user.tenantId}`, {
        headers: { Authorization: req.headers.authorization },
      }),
      axios.get(`${NOTIFICATION_SERVICE}/unread/${req.user.id}`, {
        headers: { Authorization: req.headers.authorization },
      }),
    ]);

    // Aggregate and transform for mobile
    const dashboard = {
      user: {
        name: userResponse.data.name,
        avatarUrl: userResponse.data.avatarUrl,
      },
      subscription: {
        plan: billingResponse.data.plan,
        renewsAt: billingResponse.data.currentPeriodEnd,
      },
      notifications: {
        unreadCount: notificationsResponse.data.count,
        preview: notificationsResponse.data.items.slice(0, 3),
      },
    };

    res.json(dashboard);
  } catch (error) {
    res.status(500).json({ error: 'Failed to load dashboard' });
  }
});

// Web BFF might have different aggregations
const webBFF = express();

webBFF.get('/api/web/dashboard', async (req, res) => {
  // Web dashboard includes more detailed data
  // Different aggregation pattern
});

4. Edge Gateway Pattern

Deploy gateway logic at CDN edge locations for lower latency:

// Cloudflare Workers edge gateway example
export default {
  async fetch(request: Request): Promise {
    const url = new URL(request.url);
    
    // Authenticate at edge
    const authHeader = request.headers.get('Authorization');
    if (!authHeader) {
      return new Response(JSON.stringify({ error: 'Unauthorized' }), {
        status: 401,
        headers: { 'Content-Type': 'application/json' },
      });
    }

    // Extract tenant from subdomain
    const tenant = url.hostname.split('.')[0];
    
    // Check rate limit in edge KV
    const rateLimitKey = `ratelimit:${tenant}`;
    const currentCount = await RATE_LIMITS.get(rateLimitKey);
    
    if (currentCount && parseInt(currentCount) > 1000) {
      return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), {
        status: 429,
        headers: { 'Content-Type': 'application/json' },
      });
    }

    // Route to origin based on path
    const origin = getOriginForPath(url.pathname);
    
    // Forward request with tenant context
    const modifiedRequest = new Request(origin + url.pathname + url.search, {
      method: request.method,
      headers: {
        ...Object.fromEntries(request.headers),
        'X-Tenant-ID': tenant,
      },
      body: request.body,
    });

    return fetch(modifiedRequest);
  },
};

Popular API Gateway Solutions

Gateway Best For Key Features
Kong Complex microservices Plugin ecosystem, Kubernetes-native
AWS API Gateway AWS-native apps Lambda integration, managed service
Traefik Docker/Kubernetes Auto-discovery, Let’s Encrypt
NGINX Plus High performance Load balancing, caching
Cloudflare Workers Edge computing Global distribution, low latency

Common Mistakes to Avoid

Single point of failure: Always deploy gateways in HA configuration with multiple instances and load balancing.

No circuit breakers: When backend services fail, the gateway should fail fast rather than queuing requests indefinitely.

Hardcoded configurations: Use dynamic configuration that can be updated without gateway restarts.

Missing observability: Without proper logging and metrics, debugging production issues becomes impossible.

Over-aggregation in BFF: Don’t make BFF gateways do too much work. Keep transformations simple.

Conclusion

API gateways are essential infrastructure for SaaS platforms, providing centralized control over routing, authentication, rate limiting, and observability. The single gateway pattern works well for simpler architectures, while tenant-aware gateways add the multi-tenancy features SaaS requires. BFF patterns optimize for different client types, and edge gateways minimize latency for global users. Choose the pattern—or combination of patterns—that matches your scale, team size, and operational capabilities. Start simple and evolve as your SaaS grows. For more on resilient service patterns, check out Circuit Breakers & Resilience Patterns in Microservices. For implementation guidance, explore the Kong Gateway documentation.