Skip to content

Latest commit

 

History

History
819 lines (700 loc) · 18.1 KB

File metadata and controls

819 lines (700 loc) · 18.1 KB

💡 Examples

Real-world examples and use cases for Bungate.

Table of Contents

Microservices Gateway

Enterprise-grade gateway for microservices architecture.

import { BunGateway, PinoLogger } from 'bungate'

const logger = new PinoLogger({
  level: 'info',
  enableRequestLogging: true,
})

const gateway = new BunGateway({
  server: { port: 8080 },
  cluster: {
    enabled: true,
    workers: 4,
  },
  security: {
    tls: {
      enabled: true,
      cert: './certs/cert.pem',
      key: './certs/key.pem',
      redirectHTTP: true,
      redirectPort: 80,
    },
  },
  auth: {
    secret: process.env.JWT_SECRET,
    jwtOptions: {
      algorithms: ['HS256'],
      issuer: 'https://auth.myapp.com',
      audience: 'https://api.myapp.com',
    },
    excludePaths: ['/health', '/metrics', '/auth/*', '/public/*'],
  },
  cors: {
    origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
    credentials: true,
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
  },
  metrics: { enabled: true },
  logger,
})

// User service
gateway.addRoute({
  pattern: '/users/*',
  loadBalancer: {
    strategy: 'least-connections',
    targets: [
      { url: 'http://user-service-1:3001' },
      { url: 'http://user-service-2:3001' },
    ],
    healthCheck: {
      enabled: true,
      interval: 15000,
      path: '/health',
    },
  },
  rateLimit: {
    max: 100,
    windowMs: 60000,
    keyGenerator: (req) => (req as any).user?.id || 'anonymous',
  },
})

// Payment service with circuit breaker
gateway.addRoute({
  pattern: '/payments/*',
  loadBalancer: {
    strategy: 'least-connections',
    targets: [
      { url: 'http://payment-service-1:3002' },
      { url: 'http://payment-service-2:3002' },
    ],
  },
  circuitBreaker: {
    enabled: true,
    failureThreshold: 3,
    timeout: 5000,
    resetTimeout: 30000,
  },
  timeout: 30000,
  hooks: {
    onError: async (req, error) => {
      logger.error({ error }, 'Payment service error')
      return new Response(
        JSON.stringify({
          error: 'Payment service temporarily unavailable',
          retryAfter: 30,
        }),
        {
          status: 503,
          headers: { 'Content-Type': 'application/json' },
        },
      )
    },
  },
})

// Order service
gateway.addRoute({
  pattern: '/orders/*',
  target: 'http://order-service:3003',
  middlewares: [
    async (req, next) => {
      // Inject trace ID
      const traceId = crypto.randomUUID()
      req.headers.set('X-Trace-ID', traceId)

      const response = await next()
      response.headers.set('X-Trace-ID', traceId)

      return response
    },
  ],
})

// Public endpoints
gateway.addRoute({
  pattern: '/public/*',
  target: 'http://public-api:3004',
  rateLimit: {
    max: 1000,
    windowMs: 60000,
    keyGenerator: (req) => req.headers.get('x-forwarded-for') || 'unknown',
  },
})

await gateway.listen()
console.log('Microservices gateway running on port 8080')

E-Commerce Platform

High-traffic e-commerce gateway with caching and canary deployments.

import { BunGateway } from 'bungate'

const gateway = new BunGateway({
  server: { port: 3000 },
  cluster: {
    enabled: true,
    workers: 8,
  },
})

// Product catalog with caching
const productCache = new Map<string, { data: string; expires: number }>()

gateway.addRoute({
  pattern: '/products/*',
  loadBalancer: {
    strategy: 'latency',
    targets: [
      { url: 'http://products-us-east:3000' },
      { url: 'http://products-us-west:3000' },
      { url: 'http://products-eu:3000' },
    ],
    healthCheck: {
      enabled: true,
      interval: 10000,
      path: '/health',
    },
  },
  middlewares: [
    // Cache middleware
    async (req, next) => {
      const cacheKey = req.url
      const cached = productCache.get(cacheKey)

      if (cached && cached.expires > Date.now()) {
        return new Response(cached.data, {
          headers: {
            'Content-Type': 'application/json',
            'X-Cache': 'HIT',
          },
        })
      }

      const response = await next()
      const data = await response.text()

      // Cache for 5 minutes
      productCache.set(cacheKey, {
        data,
        expires: Date.now() + 300000,
      })

      return new Response(data, {
        headers: {
          'Content-Type': 'application/json',
          'X-Cache': 'MISS',
        },
      })
    },
  ],
  rateLimit: {
    max: 10000,
    windowMs: 60000,
  },
})

// Shopping cart with sticky sessions
gateway.addRoute({
  pattern: '/cart/*',
  loadBalancer: {
    strategy: 'ip-hash',
    targets: [
      { url: 'http://cart-service-1:3001' },
      { url: 'http://cart-service-2:3001' },
      { url: 'http://cart-service-3:3001' },
    ],
    stickySession: {
      enabled: true,
      cookieName: 'cart_session',
      ttl: 3600000,
      secure: true,
      httpOnly: true,
    },
  },
})

// Checkout with weighted routing (canary deployment)
gateway.addRoute({
  pattern: '/checkout/*',
  loadBalancer: {
    strategy: 'weighted',
    targets: [
      { url: 'http://checkout-v1:3002', weight: 95 }, // Stable
      { url: 'http://checkout-v2:3003', weight: 5 }, // Canary (5%)
    ],
  },
  timeout: 45000,
})

// Order tracking
gateway.addRoute({
  pattern: '/orders/*',
  target: 'http://order-service:3004',
  auth: {
    secret: process.env.JWT_SECRET,
    jwtOptions: { algorithms: ['HS256'] },
  },
})

await gateway.listen()
console.log('E-commerce gateway running')

Multi-Tenant SaaS

Multi-tenant SaaS with tenant-based routing and rate limiting.

import { BunGateway } from 'bungate'

const gateway = new BunGateway({
  server: { port: 3000 },
})

// Extract tenant ID from subdomain or header
function getTenantId(req: Request): string {
  // From subdomain: tenant1.api.example.com
  const host = req.headers.get('host') || ''
  const subdomain = host.split('.')[0]

  // Or from header
  const tenantHeader = req.headers.get('x-tenant-id')

  return tenantHeader || subdomain || 'default'
}

// Tenant configuration
const tenantConfig = {
  'premium-tenant': { rateLimit: 10000, timeout: 60000 },
  'standard-tenant': { rateLimit: 1000, timeout: 30000 },
  'free-tenant': { rateLimit: 100, timeout: 10000 },
}

// API routes with tenant-aware rate limiting
gateway.addRoute({
  pattern: '/api/*',
  loadBalancer: {
    strategy: 'least-connections',
    targets: [
      { url: 'http://api-server-1:3001' },
      { url: 'http://api-server-2:3001' },
    ],
  },
  auth: {
    secret: process.env.JWT_SECRET,
    jwtOptions: { algorithms: ['HS256'] },
  },
  rateLimit: {
    max: 1000,
    windowMs: 60000,
    keyGenerator: (req) => {
      const tenantId = getTenantId(req)
      const userId = (req as any).user?.id || 'anonymous'
      return `${tenantId}:${userId}`
    },
  },
  middlewares: [
    // Tenant validation
    async (req, next) => {
      const tenantId = getTenantId(req)
      const config = tenantConfig[tenantId as keyof typeof tenantConfig]

      if (!config) {
        return new Response('Invalid tenant', { status: 403 })
      }

      // Inject tenant context
      req.headers.set('X-Tenant-ID', tenantId)
      req.headers.set(
        'X-Tenant-Tier',
        tenantId.startsWith('premium') ? 'premium' : 'standard',
      )

      return next()
    },
    // Usage tracking
    async (req, next) => {
      const tenantId = getTenantId(req)
      const start = Date.now()

      const response = await next()

      const duration = Date.now() - start

      // Track usage per tenant
      await trackUsage(tenantId, {
        endpoint: new URL(req.url).pathname,
        duration,
        status: response.status,
      })

      return response
    },
  ],
})

// Tenant-specific admin routes
gateway.addRoute({
  pattern: '/admin/*',
  target: 'http://admin-service:3002',
  auth: {
    secret: process.env.ADMIN_JWT_SECRET,
    jwtOptions: {
      algorithms: ['HS256'],
      audience: 'admin',
    },
  },
  middlewares: [
    async (req, next) => {
      const user = (req as any).user

      if (user?.role !== 'admin') {
        return new Response('Forbidden', { status: 403 })
      }

      return next()
    },
  ],
})

async function trackUsage(tenantId: string, usage: any) {
  // Store usage metrics
  console.log('Usage:', { tenantId, ...usage })
}

await gateway.listen()

API Marketplace

API marketplace with per-API authentication and billing.

import { BunGateway } from 'bungate'

interface APIConfig {
  id: string
  name: string
  target: string
  rateLimit: number
  pricePerRequest: number
  requiresAuth: boolean
}

const apis: Record<string, APIConfig> = {
  weather: {
    id: 'weather',
    name: 'Weather API',
    target: 'http://weather-api:3001',
    rateLimit: 1000,
    pricePerRequest: 0.001,
    requiresAuth: true,
  },
  geocoding: {
    id: 'geocoding',
    name: 'Geocoding API',
    target: 'http://geocoding-api:3002',
    rateLimit: 500,
    pricePerRequest: 0.002,
    requiresAuth: true,
  },
  currency: {
    id: 'currency',
    name: 'Currency Exchange API',
    target: 'http://currency-api:3003',
    rateLimit: 10000,
    pricePerRequest: 0.0001,
    requiresAuth: false,
  },
}

const gateway = new BunGateway({
  server: { port: 3000 },
})

// Register routes for each API
Object.values(apis).forEach((api) => {
  gateway.addRoute({
    pattern: `/api/${api.id}/*`,
    target: api.target,
    auth: api.requiresAuth
      ? {
          apiKeys: async (key: string) => {
            return await validateApiKey(key, api.id)
          },
          apiKeyHeader: 'X-API-Key',
        }
      : undefined,
    rateLimit: {
      max: api.rateLimit,
      windowMs: 60000,
      keyGenerator: (req) => {
        const apiKey = req.headers.get('x-api-key') || 'anonymous'
        return `${api.id}:${apiKey}`
      },
    },
    middlewares: [
      // Billing middleware
      async (req, next) => {
        const apiKey = req.headers.get('x-api-key')

        if (apiKey) {
          // Track request for billing
          await trackRequest(apiKey, api.id, api.pricePerRequest)
        }

        const response = await next()

        // Add usage headers
        response.headers.set('X-API-Name', api.name)
        response.headers.set('X-Cost', api.pricePerRequest.toString())

        return response
      },
      // Analytics middleware
      async (req, next) => {
        const start = Date.now()
        const response = await next()
        const duration = Date.now() - start

        await trackAnalytics(api.id, {
          path: new URL(req.url).pathname,
          method: req.method,
          status: response.status,
          duration,
        })

        return response
      },
    ],
  })
})

async function validateApiKey(key: string, apiId: string): Promise<boolean> {
  // Validate key has access to this API
  // Check subscription status, quotas, etc.
  return true // Placeholder
}

async function trackRequest(apiKey: string, apiId: string, cost: number) {
  // Track request for billing
  console.log('Billing:', { apiKey, apiId, cost })
}

async function trackAnalytics(apiId: string, data: any) {
  // Store analytics
  console.log('Analytics:', { apiId, ...data })
}

await gateway.listen()

Content Delivery

CDN-like content delivery with caching and geo-routing.

import { BunGateway } from 'bungate'

const gateway = new BunGateway({
  server: { port: 3000 },
  cluster: {
    enabled: true,
    workers: 8,
  },
})

// In-memory cache
const cache = new Map<
  string,
  { data: Buffer; contentType: string; expires: number }
>()

// Static assets with aggressive caching
gateway.addRoute({
  pattern: '/static/*',
  loadBalancer: {
    strategy: 'latency',
    targets: [
      { url: 'http://cdn-us:3001' },
      { url: 'http://cdn-eu:3001' },
      { url: 'http://cdn-asia:3001' },
    ],
  },
  middlewares: [
    // Cache middleware
    async (req, next) => {
      const cacheKey = req.url
      const cached = cache.get(cacheKey)

      if (cached && cached.expires > Date.now()) {
        return new Response(cached.data, {
          headers: {
            'Content-Type': cached.contentType,
            'Cache-Control': 'public, max-age=31536000',
            'X-Cache': 'HIT',
          },
        })
      }

      const response = await next()
      const data = await response.arrayBuffer()
      const contentType =
        response.headers.get('content-type') || 'application/octet-stream'

      // Cache for 1 hour
      cache.set(cacheKey, {
        data: Buffer.from(data),
        contentType,
        expires: Date.now() + 3600000,
      })

      return new Response(data, {
        headers: {
          'Content-Type': contentType,
          'Cache-Control': 'public, max-age=31536000',
          'X-Cache': 'MISS',
        },
      })
    },
  ],
})

// Images with optimization
gateway.addRoute({
  pattern: '/images/*',
  target: 'http://image-service:3002',
  middlewares: [
    async (req, next) => {
      // Add image optimization parameters
      const url = new URL(req.url)
      const width = url.searchParams.get('w')
      const quality = url.searchParams.get('q') || '80'

      if (width) {
        url.searchParams.set('width', width)
      }
      url.searchParams.set('quality', quality)

      return next()
    },
  ],
})

// Videos with streaming
gateway.addRoute({
  pattern: '/videos/*',
  loadBalancer: {
    strategy: 'least-connections',
    targets: [
      { url: 'http://video-server-1:3003' },
      { url: 'http://video-server-2:3003' },
    ],
  },
  timeout: 300000, // 5 minutes for large videos
})

await gateway.listen()

WebSocket Gateway

WebSocket gateway with connection management.

import { BunGateway } from 'bungate'

const gateway = new BunGateway({
  server: { port: 3000 },
})

// WebSocket connections with sticky sessions
gateway.addRoute({
  pattern: '/ws',
  loadBalancer: {
    strategy: 'ip-hash', // Ensure same client goes to same server
    targets: [
      { url: 'ws://ws-server-1:3001' },
      { url: 'ws://ws-server-2:3001' },
      { url: 'ws://ws-server-3:3001' },
    ],
    healthCheck: {
      enabled: true,
      interval: 10000,
      path: '/health',
    },
  },
  auth: {
    secret: process.env.JWT_SECRET,
    jwtOptions: { algorithms: ['HS256'] },
    getToken: (req) => {
      // Get token from query parameter for WebSocket
      return new URL(req.url).searchParams.get('token')
    },
  },
})

// REST API for WebSocket management
gateway.addRoute({
  pattern: '/api/connections',
  target: 'http://connection-manager:3002',
  auth: {
    secret: process.env.JWT_SECRET,
    jwtOptions: { algorithms: ['HS256'] },
  },
})

await gateway.listen()

Development Proxy

Development proxy with hot reload support.

import { BunGateway } from 'bungate'

const isDev = process.env.NODE_ENV !== 'production'

const gateway = new BunGateway({
  server: {
    port: 3000,
    development: isDev,
  },
})

// Frontend dev server
gateway.addRoute({
  pattern: '/',
  target: 'http://localhost:5173', // Vite dev server
  proxy: {
    preserveHostHeader: true,
  },
})

// Backend API
gateway.addRoute({
  pattern: '/api/*',
  target: 'http://localhost:3001',
  middlewares: [
    // Logging middleware for development
    async (req, next) => {
      console.log('→', req.method, req.url)
      const start = Date.now()

      const response = await next()

      const duration = Date.now() - start
      console.log('←', response.status, `(${duration}ms)`)

      return response
    },
  ],
})

// Mock API endpoints for development
if (isDev) {
  gateway.addRoute({
    pattern: '/api/mock/*',
    handler: async (req) => {
      return new Response(JSON.stringify({ mock: true, data: [] }), {
        headers: { 'Content-Type': 'application/json' },
      })
    },
  })
}

await gateway.listen()
console.log('Development proxy running on http://localhost:3000')

Canary Deployments

Gradual rollout with monitoring and automatic rollback.

import { BunGateway } from 'bungate'

let canaryWeight = 5 // Start with 5%
const gateway = new BunGateway({
  server: { port: 3000 },
})

gateway.addRoute({
  pattern: '/api/*',
  loadBalancer: {
    strategy: 'weighted',
    targets: [
      { url: 'http://app-v1:3001', weight: 100 - canaryWeight },
      { url: 'http://app-v2:3002', weight: canaryWeight },
    ],
    healthCheck: {
      enabled: true,
      interval: 10000,
      path: '/health',
    },
  },
  middlewares: [
    // Track errors per version
    async (req, next) => {
      const response = await next()

      const version = response.headers.get('x-app-version') || 'unknown'

      if (response.status >= 500) {
        await trackError(version)
      }

      return response
    },
  ],
})

// Gradually increase canary traffic
setInterval(async () => {
  const errorRate = await getErrorRate('v2')

  if (errorRate < 0.01) {
    // Less than 1% error rate
    if (canaryWeight < 100) {
      canaryWeight = Math.min(100, canaryWeight + 10)
      console.log(`Increasing canary to ${canaryWeight}%`)

      // Update route (requires restart or dynamic update)
      // In production, use configuration management
    }
  } else {
    console.log('High error rate detected, pausing rollout')
  }
}, 60000) // Every minute

async function trackError(version: string) {
  // Track errors
  console.log('Error in version:', version)
}

async function getErrorRate(version: string): Promise<number> {
  // Get error rate from metrics
  return 0.005 // Placeholder
}

await gateway.listen()

Related Documentation


More examples? Check the examples directory for working code samples.