Claude Code for API Gateways: Rate Limiting, Authentication, and Routing — Claude Skills 360 Blog
Blog / Development / Claude Code for API Gateways: Rate Limiting, Authentication, and Routing
Development

Claude Code for API Gateways: Rate Limiting, Authentication, and Routing

Published: July 29, 2026
Read time: 8 min read
By: Claude Skills 360

An API gateway centralizes cross-cutting concerns — authentication, rate limiting, routing, circuit breaking — so individual services don’t implement them separately. Claude Code generates gateway middleware, Kong plugin configurations, and the patterns that make gateways maintainable without becoming a bottleneck.

Express API Gateway Middleware

Build an API gateway in Express.
Features: JWT auth, per-user rate limiting (100 req/min),
request routing to microservices, circuit breaker to handle downstream failures.

JWT Authentication Middleware

// middleware/auth.ts
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';

export interface AuthenticatedRequest extends Request {
  user?: {
    userId: string;
    role: string;
    scopes: string[];
  };
}

export function authenticate(req: AuthenticatedRequest, res: Response, next: NextFunction) {
  const authHeader = req.headers.authorization;

  if (!authHeader?.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'Missing authorization header' });
  }

  const token = authHeader.slice(7);

  try {
    const payload = jwt.verify(token, process.env.JWT_PUBLIC_KEY!, {
      algorithms: ['RS256'], // Asymmetric — public key validation only
    }) as any;

    req.user = {
      userId: payload.sub,
      role: payload.role,
      scopes: payload.scopes ?? [],
    };

    next();
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
    }
    return res.status(401).json({ error: 'Invalid token' });
  }
}

export function requireScope(scope: string) {
  return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    if (!req.user?.scopes.includes(scope)) {
      return res.status(403).json({ error: `Missing required scope: ${scope}` });
    }
    next();
  };
}

Rate Limiting

// middleware/rateLimiter.ts
import { Redis } from 'ioredis';

const redis = new Redis(process.env.REDIS_URL!);

// Sliding window rate limiter using Redis sorted sets
export function rateLimit(options: {
  windowMs: number;
  max: number;
  keyGenerator: (req: Request) => string;
}) {
  return async (req: Request, res: Response, next: NextFunction) => {
    const key = `rate-limit:${options.keyGenerator(req)}`;
    const now = Date.now();
    const windowStart = now - options.windowMs;

    const pipeline = redis.pipeline();
    pipeline.zremrangebyscore(key, 0, windowStart);       // Remove old requests
    pipeline.zadd(key, now, `${now}-${Math.random()}`);   // Record this request
    pipeline.zcard(key);                                   // Count requests in window
    pipeline.expire(key, Math.ceil(options.windowMs / 1000)); // Auto-cleanup

    const results = await pipeline.exec();
    const requestCount = results?.[2]?.[1] as number;

    res.setHeader('X-RateLimit-Limit', options.max);
    res.setHeader('X-RateLimit-Remaining', Math.max(0, options.max - requestCount));
    res.setHeader('X-RateLimit-Reset', Math.ceil((now + options.windowMs) / 1000));

    if (requestCount > options.max) {
      res.setHeader('Retry-After', Math.ceil(options.windowMs / 1000));
      return res.status(429).json({
        error: 'Too many requests',
        retryAfter: Math.ceil(options.windowMs / 1000),
      });
    }

    next();
  };
}

// Per-user rate limiting
export const userRateLimit = rateLimit({
  windowMs: 60 * 1000,   // 1 minute window
  max: 100,
  keyGenerator: (req) => (req as AuthenticatedRequest).user?.userId ?? req.ip,
});

// Stricter rate limit for expensive endpoints
export const heavyEndpointLimit = rateLimit({
  windowMs: 60 * 1000,
  max: 10,
  keyGenerator: (req) => (req as AuthenticatedRequest).user?.userId ?? req.ip,
});

Circuit Breaker

// middleware/circuitBreaker.ts
enum CircuitState { CLOSED, OPEN, HALF_OPEN }

interface CircuitBreakerOptions {
  failureThreshold: number;  // Open circuit after N failures
  resetTimeout: number;      // Try again after N ms
  successThreshold: number;  // Close circuit after N successes in HALF_OPEN
}

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

  constructor(private options: CircuitBreakerOptions) {}

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

    try {
      const result = await fn();

      if (this.state === CircuitState.HALF_OPEN) {
        this.successCount++;
        if (this.successCount >= this.options.successThreshold) {
          this.state = CircuitState.CLOSED;
          this.failureCount = 0;
        }
      } else {
        this.failureCount = 0; // Reset on success in CLOSED state
      }

      return result;
    } catch (error) {
      this.failureCount++;
      this.lastFailureTime = Date.now();

      if (this.failureCount >= this.options.failureThreshold) {
        this.state = CircuitState.OPEN;
      }

      throw error;
    }
  }
}

// Service proxies with circuit breakers
const serviceBreakers = {
  userService: new CircuitBreaker({ failureThreshold: 5, resetTimeout: 30000, successThreshold: 2 }),
  orderService: new CircuitBreaker({ failureThreshold: 5, resetTimeout: 30000, successThreshold: 2 }),
};

export async function proxyToService(serviceUrl: string, service: keyof typeof serviceBreakers, req: Request, res: Response) {
  try {
    const response = await serviceBreakers[service].call(async () => {
      const result = await fetch(`${serviceUrl}${req.path}`, {
        method: req.method,
        headers: {
          ...req.headers as any,
          host: new URL(serviceUrl).host,
          'x-user-id': (req as AuthenticatedRequest).user?.userId ?? '',
          'x-correlation-id': req.headers['x-correlation-id'] ?? crypto.randomUUID(),
        },
        body: ['POST', 'PUT', 'PATCH'].includes(req.method) ? JSON.stringify(req.body) : undefined,
        signal: AbortSignal.timeout(5000), // 5s timeout
      });

      if (!result.ok) throw new Error(`Service returned ${result.status}`);
      return result;
    });

    const data = await response.json();
    res.status(response.status).json(data);
  } catch (error: any) {
    if (error.message.includes('Circuit breaker is OPEN')) {
      res.status(503).json({ error: 'Service temporarily unavailable', retryAfter: 30 });
    } else {
      res.status(502).json({ error: 'Upstream service error' });
    }
  }
}

Route Configuration

// gateway.ts — assemble all middleware
import express from 'express';
import { authenticate, requireScope } from './middleware/auth';
import { userRateLimit, heavyEndpointLimit } from './middleware/rateLimiter';
import { proxyToService } from './middleware/circuitBreaker';

const app = express();
app.use(express.json());

// All routes require authentication
app.use(authenticate);

// User service routes
app.use('/api/v1/users', userRateLimit, (req, res) =>
  proxyToService(process.env.USER_SERVICE_URL!, 'userService', req, res)
);

// Order service routes — with scope requirement
app.use('/api/v1/orders', userRateLimit,
  requireScope('orders:read'),
  (req, res) => proxyToService(process.env.ORDER_SERVICE_URL!, 'orderService', req, res)
);

// Admin routes — heavy rate limit + admin scope
app.use('/api/v1/admin',
  heavyEndpointLimit,
  requireScope('admin'),
  (req, res) => proxyToService(process.env.ADMIN_SERVICE_URL!, 'userService', req, res)
);

app.listen(8080, () => console.log('Gateway on :8080'));

Kong Configuration

We're using Kong as our API gateway. Configure rate limiting,
JWT authentication, and request transformation plugins for our API.
# kong.yaml — declarative configuration
_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: jwt
        config:
          secret_is_base64: false
          key_claim_name: iss
      - name: rate-limiting
        config:
          minute: 100
          policy: redis
          redis_host: redis
          redis_port: 6379
      - name: request-transformer
        config:
          add:
            headers:
              - "X-Gateway-Version:1.0"
              - "X-Request-ID:$(uuid)"
          remove:
            headers:
              - Cookie  # Strip cookies before forwarding

  - name: public-endpoints
    url: http://api-service:3000
    routes:
      - name: health-route
        paths: [/health]
        # No auth plugin — public endpoint

For securing the authentication flows these gateways enforce, see the authentication guide. For microservices that sit behind this gateway, see the microservices guide. The Claude Skills 360 bundle includes API gateway skill sets for rate limiting, circuit breakers, and Kong configuration. Start with the free tier to try gateway middleware generation.

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free