DEV Community

Cover image for The Complete Guide to @hazeljs/gateway: Intelligent API Gateway for Microservices
Muhammad Arslan
Muhammad Arslan

Posted on

The Complete Guide to @hazeljs/gateway: Intelligent API Gateway for Microservices

A practical, in-depth exploration of the HazelJS API Gateway — from routing and versioning to canary deployments and production-ready patterns.


Table of Contents

  1. Introduction
  2. Why an API Gateway?
  3. Architecture Overview
  4. Core Concepts
  5. Getting Started
  6. Version Routing
  7. Canary Deployments
  8. Resilience Patterns
  9. Traffic Management
  10. Production Integration
  11. Best Practices

Introduction

The @hazeljs/gateway package is an intelligent API gateway designed for HazelJS microservices. It sits between your clients and backend services, providing:

  • Unified entry point — Clients talk to one URL; the gateway routes to the right service
  • Version routing — Route by header, URI, query param, or weighted distribution
  • Canary deployments — Gradually shift traffic with automatic promotion or rollback
  • Resilience — Circuit breaker, rate limiting, retries, and timeouts per route
  • Traffic mirroring — Shadow traffic to test new versions without affecting users
  • Real-time metrics — Per-route and per-version performance tracking

This guide walks you through each feature with practical examples and diagrams.


Why an API Gateway?

In a microservices architecture, clients would otherwise need to know about every service:

Without Gateway:
┌─────────┐     ┌─────────────────┐
│ Client  │────▶│ user-service    │ :3001
└─────────┘     └─────────────────┘
       │
       │        ┌─────────────────┐
       ├───────▶│ order-service   │ :3002
       │        └─────────────────┘
       │
       │        ┌─────────────────┐
       └───────▶│ payment-service │ :3003
                └─────────────────┘

Problems: Multiple URLs, CORS, auth per service, no central control
Enter fullscreen mode Exit fullscreen mode

With a gateway, clients have a single entry point:

With Gateway:
┌─────────┐     ┌──────────────────────────────────────────┐
│ Client  │────▶│              API Gateway                 │
└─────────┘     │  /api/users   → user-service             │
                │  /api/orders  → order-service             │
                │  /api/payments→ payment-service          │
                └──────────────────────────────────────────┘
                              │
              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
       ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
       │ user-svc    │ │ order-svc   │ │ payment-svc │
       └─────────────┘ └─────────────┘ └─────────────┘

Benefits: Single URL, central auth, routing, resilience, metrics
Enter fullscreen mode Exit fullscreen mode

Architecture Overview

High-Level Flow

                    ┌─────────────────┐
                    │   HTTP Client   │
                    └────────┬────────┘
                             │ GET /api/users
                             ▼
┌────────────────────────────────────────────────────────────────────────────┐
│                         GATEWAY LAYER                                        │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────┐   ┌─────────────┐ │
│  │Route Matcher │──▶│Version Router│──▶│Canary Engine │──▶│ServiceProxy │ │
│  └──────────────┘   └──────────────┘   └──────────────┘   └──────┬──────┘ │
│                                                                   │        │
│  ┌──────────────┐   ┌──────────────┐                             │        │
│  │CircuitBreaker│◀──│ Rate Limiter │◀────────────────────────────┘        │
│  └──────────────┘   └──────────────┘                                       │
└────────────────────────────────────────────────────────────────────────────┘
                             │
                             ▼
                    ┌─────────────────┐
                    │ DiscoveryClient │
                    └────────┬────────┘
                             │ Load balanced
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
       ┌───────────┐  ┌───────────┐  ┌───────────┐
       │user-svc v1│  │user-svc v2│  │order-svc  │
       └───────────┘  └───────────┘  └───────────┘
Enter fullscreen mode Exit fullscreen mode

Request Lifecycle

Request:  Client ──▶ Gateway ──▶ Route Match ──▶ Version Select ──▶ Canary Engine
                                                         │
                                                         ▼
              Service Proxy (circuit breaker + rate limit) ──▶ Discovery ──▶ Backend

Response: Client ◀── Gateway ◀── Service Proxy ◀── Backend
Enter fullscreen mode Exit fullscreen mode

Key Components

Component Responsibility
GatewayServer Main orchestrator; matches routes, applies policies, forwards requests
Route Matcher Glob patterns, path params (:id), wildcards (*, **)
Version Router Resolves target version from header, URI, query, or weighted random
Canary Engine Manages canary lifecycle — traffic weights, metrics, promotion, rollback
Service Proxy HTTP client that resolves instances via discovery, applies resilience
Circuit Breaker Per-route protection; opens when failure threshold exceeded
Rate Limiter Token bucket or sliding window; returns 429 when exceeded
Gateway Metrics Per-route and per-version success/failure/latency tracking

Core Concepts

Route Matching

The gateway supports flexible path patterns:

Pattern Matches Example
/api/users Exact Only /api/users
/api/users/:id Path parameter /api/users/123{ id: '123' }
/api/users/* Single segment /api/users/123 but not /api/users/123/orders
/api/users/** Multi-segment /api/users/123/orders/456

Routes are sorted by specificity — more specific patterns match first:

1. /api/users/:id/orders   (most specific)
2. /api/users/:id
3. /api/users/*
4. /api/users
5. /api/**
6. /**                    (catch-all)
Enter fullscreen mode Exit fullscreen mode

Path Rewriting

The gateway can transform paths before forwarding:

{
  path: '/api/users/**',
  serviceName: 'user-service',
  serviceConfig: {
    stripPrefix: '/api/users',  // Remove from incoming path
    addPrefix: '/users',        // Add to forwarded path
  },
}
Enter fullscreen mode Exit fullscreen mode

Example: GET /api/users/123 → forwarded as GET /users/123 to user-service.

Service Discovery Integration

The gateway uses @hazeljs/discovery to find backend instances. It supports:

  • Memory backend — Development (in-memory)
  • Redis backend — Production (shared state)
  • Consul backend — Service mesh
  • Kubernetes backend — K8s Endpoints API

Load balancing strategies: round-robin, random, least-connections, weighted-round-robin, IP hash, zone-aware.


Getting Started

1. Install Dependencies

npm install @hazeljs/gateway @hazeljs/discovery @hazeljs/resilience
Enter fullscreen mode Exit fullscreen mode

2. Minimal Config-Driven Gateway

import { GatewayServer } from '@hazeljs/gateway';
import { MemoryRegistryBackend } from '@hazeljs/discovery';

const backend = new MemoryRegistryBackend();

// Register your services with the backend (see discovery docs)
// await registerUserService(backend, 3001);

const gateway = GatewayServer.fromConfig(
  {
    routes: [
      {
        path: '/api/users/**',
        serviceName: 'user-service',
        serviceConfig: {
          stripPrefix: '/api/users',
          addPrefix: '/users',
        },
      },
    ],
  },
  backend
);

// Handle a request
const response = await gateway.handleRequest({
  method: 'GET',
  path: '/api/users',
  headers: { 'content-type': 'application/json' },
});
Enter fullscreen mode Exit fullscreen mode

3. Run the Demos

From the hazeljs-gateway-starter directory:

# Start mock backend services
npm run start:services

# Config-driven gateway
npm run demo:gateway:config

# Gateway with HazelApp (production-style)
npm run demo:gateway:hazelapp

# Full-stack integration
npm run demo:scenario:full-stack
Enter fullscreen mode Exit fullscreen mode

Version Routing

Version routing lets you serve multiple API versions from the same gateway path.

Strategy: Header

Clients opt-in by sending X-API-Version: v2:

{
  path: '/api/users/**',
  serviceName: 'user-service',
  versionRoute: {
    strategy: 'header',
    header: 'X-API-Version',
    defaultVersion: 'v1',
    routes: {
      v1: { weight: 80, allowExplicit: true, filter: { tags: ['v1'] } },
      v2: { weight: 20, allowExplicit: true, filter: { tags: ['v2'] } },
    },
  },
}
Enter fullscreen mode Exit fullscreen mode
Client A (no header) ──────────▶ Gateway ──▶ user-service v1 (80% traffic)
Client B (X-API-Version: v2) ──▶ Gateway ──▶ user-service v2 (20% traffic)
Enter fullscreen mode Exit fullscreen mode

Strategy: URI Prefix

RESTful versioning: /v1/api/users, /v2/api/users

Strategy: Query Parameter

Quick testing: ?version=v2

Strategy: Weighted

A/B testing: 80% to v1, 20% to v2 (random distribution)


Canary Deployments

Canary deployments gradually shift traffic from a stable version to a new version. The gateway monitors error rates and latency, then automatically promotes or rolls back.

How It Works

Deploy v2 ──▶ 10% canary ──▶ Monitor 5 min
                                    │
                    ┌───────────────┴───────────────┐
                    │                               │
              errors < 5%                     errors > 5%
                    │                               │
                    ▼                               ▼
              25% ──▶ 50% ──▶ 75% ──▶ 100% ✓    Rollback 0% ✗
                    (Complete)                  (Abort)
Enter fullscreen mode Exit fullscreen mode

Configuration

{
  path: '/api/orders/**',
  serviceName: 'order-service',
  canary: {
    stable: {
      version: '1.0.0',
      weight: 90,
      filter: { metadata: { version: '1.0.0' } },
    },
    canary: {
      version: '2.0.0',
      weight: 10,
      filter: { metadata: { version: '2.0.0' } },
    },
    promotion: {
      strategy: 'error-rate',
      errorThreshold: 5,           // Rollback if errors > 5%
      evaluationWindow: '5m',      // Evaluate over 5 minutes
      autoPromote: true,
      autoRollback: true,
      steps: [10, 25, 50, 75, 100],
      stepInterval: '10m',
      minRequests: 10,             // Wait for enough data
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

Promotion Strategies

Strategy Metric Use Case
error-rate % of 5xx responses Most common — catches bugs
latency p99 or average threshold Performance regressions
custom Your evaluation function Multi-metric decisions

Events

gateway.on('canary:promote', (data) => {
  console.log(`Step ${data.step}: canary at ${data.canaryWeight}%`);
});

gateway.on('canary:rollback', (data) => {
  console.log(`Rolled back: ${data.canaryVersion} (trigger: ${data.trigger})`);
});

gateway.on('canary:complete', (data) => {
  console.log(`${data.version} is now at 100%`);
});
Enter fullscreen mode Exit fullscreen mode

Manual Control

const engine = gateway.getCanaryEngine('/api/orders/**');

engine.pause();   // Freeze canary progression
engine.resume();  // Resume
engine.promote(); // Force next step
engine.rollback(); // Force rollback to 0%
Enter fullscreen mode Exit fullscreen mode

Resilience Patterns

The gateway uses @hazeljs/resilience for per-route protection.

Circuit Breaker

Prevents cascading failures by opening the circuit when too many requests fail:

     CLOSED (Normal)
          │
          │ failures >= threshold
          ▼
     OPEN (Blocking)
          │
          │ reset timeout
          ▼
     HALF_OPEN (Testing)
          │
    ┌─────┴─────┐
    │           │
 success    failure
    │           │
    ▼           ▼
 CLOSED       OPEN
Enter fullscreen mode Exit fullscreen mode
{
  path: '/api/payments/**',
  serviceName: 'payment-service',
  circuitBreaker: {
    failureThreshold: 3,
    successThreshold: 2,
    resetTimeout: 15_000,
    slidingWindow: { type: 'time', size: 60_000 },
  },
}
Enter fullscreen mode Exit fullscreen mode

Rate Limiting

Returns 429 Too Many Requests with Retry-After header when exceeded:

{
  path: '/api/orders/**',
  rateLimit: {
    strategy: 'sliding-window',
    max: 100,
    window: 60_000,  // 100 requests per minute
  },
}
Enter fullscreen mode Exit fullscreen mode

Strategies: token-bucket (allows bursts) or sliding-window (rolling limit).

Retry & Timeout

trafficPolicy: {
  timeout: 8_000,
  retry: {
    maxAttempts: 3,
    backoff: 'exponential',
    baseDelay: 1_000,
    maxDelay: 10_000,
    jitter: true,
  },
}
Enter fullscreen mode Exit fullscreen mode

Traffic Management

Traffic Mirroring

Send a percentage of traffic to a shadow service without affecting the response:

{
  path: '/api/products/**',
  serviceName: 'product-service',
  trafficPolicy: {
    mirror: {
      service: 'analytics-service',
      percentage: 30,
      waitForResponse: false,  // Fire-and-forget
    },
    timeout: 5_000,
  },
}
Enter fullscreen mode Exit fullscreen mode

Use cases: Testing new versions with real traffic, comparing responses, performance benchmarking.

Request/Response Transforms

Modify headers or body in transit (see gateway docs for transform API).


Production Integration

Gateway with HazelApp

The recommended production setup: run the gateway behind HazelApp's HTTP server. This gives you:

  • Health checks (/health, /ready, /live)
  • CORS, body parsing, request ID
  • Graceful shutdown
  • Single process for gateway + custom controllers
import { HazelApp, HazelModule } from '@hazeljs/core';
import { GatewayServer, createGatewayHandler } from '@hazeljs/gateway';

const gateway = GatewayServer.fromConfig(config, backend);
gateway.startCanaries();

const app = new HazelApp(AppModule);
app.addProxyHandler('/api', createGatewayHandler(gateway));
await app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Flow: Requests to /api/* go to the gateway; other paths (e.g. /health) go to HazelApp controllers.

Config from Environment Variables

For 12-factor apps, drive config from env vars:

const gatewayConfig = () => ({
  gateway: {
    routes: [
      {
        path: '/api/orders/**',
        serviceName: 'order-service',
        canary: {
          stable: {
            version: process.env.ORDER_STABLE_VERSION || 'v1',
            weight: parseInt(process.env.ORDER_STABLE_WEIGHT || '90'),
          },
          canary: {
            version: process.env.ORDER_CANARY_VERSION || 'v2',
            weight: parseInt(process.env.ORDER_CANARY_WEIGHT || '10'),
          },
          promotion: {
            errorThreshold: parseInt(process.env.ORDER_CANARY_ERROR_THRESHOLD || '5'),
            evaluationWindow: process.env.ORDER_CANARY_WINDOW || '5m',
          },
        },
      },
    ],
  },
});
Enter fullscreen mode Exit fullscreen mode

Best Practices

1. Use Config-Driven Routes in Production

Decorators are great for prototyping; config-driven routes let you change behavior without redeploying.

2. Start with Conservative Canary Weights

Begin with 5–10% canary traffic. Increase via env vars as confidence grows.

3. Set Appropriate Evaluation Windows

  • Too short: Not enough data, noisy decisions
  • Too long: Users exposed to bugs longer
  • 5 minutes is a good default

4. Monitor Canary Events

Always log canary:promote, canary:rollback, and canary:complete to your monitoring system.

5. Circuit Breakers on Every Route

Even if you trust downstream services, circuit breakers prevent cascading failures during outages.

6. Per-Route Rate Limits

Different services have different capacity. Set limits based on each service's throughput.

7. Traffic Mirroring Before Canary

Before running a canary, mirror traffic to the new version to compare responses and catch obvious bugs.

8. Keep the Gateway Stateless

All state (canary weights, circuit breaker state) is in-memory. On restart, the gateway rebuilds from config.


Summary

Feature Use Case
Version routing Multiple API versions, A/B testing
Canary deployments Safe rollouts with automatic rollback
Circuit breaker Prevent cascading failures
Rate limiting Protect downstream services
Traffic mirroring Test with real traffic
Path rewriting Clean API surface for clients
Metrics Observability and canary decisions

The @hazeljs/gateway package integrates seamlessly with @hazeljs/discovery and @hazeljs/resilience to provide a production-ready API gateway for your HazelJS microservices.


Further Reading

Top comments (0)