A sophisticated development proxy that routes traffic from multiple local services through a single endpoint with integrated ngrok tunnel support.
Features • Installation • Quick Start • Examples • Configuration
- Multi-Service Routing - Route requests to different backends based on path, headers, or subdomains
- Integrated Tunnel - Built-in ngrok integration for external access with a single command
- Hot-Reload Configuration - Changes to
hz.yamlapply automatically without restart - Health Checking - Automatic service health monitoring with status tracking
- WebSocket Support - Full bidirectional WebSocket proxy support
- CLI Management - Simple commands to manage services and configuration
brew install zymawy/hz/hzOr with explicit tap:
brew tap zymawy/hz
brew install zymawy/hz/hzgo install github.com/zymawy/hz@latestgit clone https://github.com/zymawy/hz.git
cd hz
go build -o hz .Download the latest release from the releases page.
# Initialize configuration
hz init
# Add your services
hz add backend 3001 --default
hz add api 8080 --route '/api/*'
hz add websocket 9000 --route 'header:upgrade=websocket'
# Start the proxy
hz start
# Enable external access via ngrok
hz tunnel --enableRoute requests to different services based on custom HTTP headers. Perfect for microservices development where you need to route based on service identifiers.
Use Case: Route requests with b-service: sabry header to a specific backend
# hz.yaml
services:
- name: sabry-service
target: "http://localhost:3008"
routes:
- header: "b-service=sabry"
- name: ahmed-service
target: "http://localhost:3009"
routes:
- header: "b-service=ahmed"
- name: default-backend
target: "http://localhost:3001"
default: trueCLI Setup:
hz add sabry-service 3008 --route 'header:b-service=sabry'
hz add ahmed-service 3009 --route 'header:b-service=ahmed'
hz add default-backend 3001 --defaultTesting:
# Routes to localhost:3008
curl -H "b-service: sabry" http://localhost:3000/users
# Routes to localhost:3009
curl -H "b-service: ahmed" http://localhost:3000/users
# Routes to localhost:3001 (default)
curl http://localhost:3000/usersRoute different API versions or modules to separate backend services.
Use Case: Microservices with separate services for users, orders, and payments
# hz.yaml
services:
- name: users-api
target: "http://localhost:3001"
routes:
- path: "/api/users/*"
- path: "/api/auth/*"
rewrite:
stripPrefix: "/api"
- name: orders-api
target: "http://localhost:3002"
routes:
- path: "/api/orders/*"
rewrite:
stripPrefix: "/api"
- name: payments-api
target: "http://localhost:3003"
routes:
- path: "/api/payments/*"
rewrite:
stripPrefix: "/api"
- name: frontend
target: "http://localhost:3000"
default: trueCLI Setup:
hz add users-api 3001 --route '/api/users/*'
hz add orders-api 3002 --route '/api/orders/*'
hz add payments-api 3003 --route '/api/payments/*'
hz add frontend 3000 --defaultTesting:
# Routes to users-api (localhost:3001/users/123)
curl http://localhost:3000/api/users/123
# Routes to orders-api (localhost:3002/orders)
curl http://localhost:3000/api/orders
# Routes to payments-api (localhost:3003/payments/charge)
curl -X POST http://localhost:3000/api/payments/charge
# Routes to frontend (localhost:3000)
curl http://localhost:3000/Proxy WebSocket connections for real-time applications like chat, notifications, or live updates.
Use Case: Real-time chat application with WebSocket backend
# hz.yaml
services:
- name: websocket-server
target: "http://localhost:9000"
routes:
- header: "upgrade=websocket"
- path: "/ws/*"
- name: socket-io
target: "http://localhost:9001"
routes:
- path: "/socket.io/*"
- name: main-app
target: "http://localhost:3000"
default: trueCLI Setup:
hz add websocket-server 9000 --route 'header:upgrade=websocket'
hz add socket-io 9001 --route '/socket.io/*'
hz add main-app 3000 --defaultTesting:
# WebSocket connection
wscat -c ws://localhost:3000/ws/chat
# Socket.io connection (handled automatically by socket.io client)
# In browser: io.connect('http://localhost:3000')Route requests based on subdomain for multi-tenant applications.
Use Case: SaaS application with tenant-specific backends
# hz.yaml
services:
- name: admin-panel
target: "http://localhost:3001"
routes:
- subdomain: "admin"
- name: api-service
target: "http://localhost:3002"
routes:
- subdomain: "api"
- name: docs-site
target: "http://localhost:3003"
routes:
- subdomain: "docs"
- name: main-app
target: "http://localhost:3000"
default: trueCLI Setup:
hz add admin-panel 3001 --route 'subdomain:admin'
hz add api-service 3002 --route 'subdomain:api'
hz add docs-site 3003 --route 'subdomain:docs'
hz add main-app 3000 --defaultTesting (requires local DNS or /etc/hosts):
# Add to /etc/hosts:
# 127.0.0.1 admin.myapp.local api.myapp.local docs.myapp.local myapp.local
curl http://admin.myapp.local:3000/ # Routes to admin-panel
curl http://api.myapp.local:3000/ # Routes to api-service
curl http://docs.myapp.local:3000/ # Routes to docs-site
curl http://myapp.local:3000/ # Routes to main-appExpose local services for webhook testing from external services (Stripe, GitHub, etc.).
Use Case: Testing Stripe webhooks locally
# hz.yaml
server:
port: 3000
tunnel:
enabled: true
provider: ngrok
authtoken: "${NGROK_AUTHTOKEN}"
domain: "myapp.ngrok.io" # Optional: custom domain
services:
- name: webhook-handler
target: "http://localhost:3001"
routes:
- path: "/webhooks/*"
- name: main-app
target: "http://localhost:3000"
default: trueCLI Setup:
export NGROK_AUTHTOKEN=your_token_here
hz add webhook-handler 3001 --route '/webhooks/*'
hz add main-app 3000 --default
hz tunnel --enable
hz startOutput:
🚀 hz proxy starting...
Local: http://0.0.0.0:3000
Public: https://myapp.ngrok.io
📦 Services:
• webhook-handler → http://localhost:3001
• main-app → http://localhost:3000 (default)
Now configure Stripe webhook URL: https://myapp.ngrok.io/webhooks/stripe
Route requests to different service versions based on custom headers for A/B testing or feature branch testing.
Use Case: Testing new API version alongside production
# hz.yaml
services:
- name: api-v2-beta
target: "http://localhost:3002"
routes:
- header: "x-api-version=v2"
- header: "x-feature-flag=new-checkout"
- name: api-v1-stable
target: "http://localhost:3001"
default: trueCLI Setup:
hz add api-v2-beta 3002 --route 'header:x-api-version=v2'
hz add api-v1-stable 3001 --defaultTesting:
# Test new version
curl -H "x-api-version: v2" http://localhost:3000/checkout
# Production version
curl http://localhost:3000/checkoutUse multiple routing conditions for complex scenarios.
Use Case: E-commerce platform with multiple services
# hz.yaml
services:
# Mobile API (detected by user-agent or custom header)
- name: mobile-api
target: "http://localhost:4001"
routes:
- header: "x-client=mobile"
- path: "/mobile/*"
# Admin Panel
- name: admin
target: "http://localhost:4002"
routes:
- subdomain: "admin"
- path: "/admin/*"
# Real-time notifications
- name: notifications
target: "http://localhost:4003"
routes:
- path: "/notifications/*"
- header: "upgrade=websocket"
# Search service
- name: search
target: "http://localhost:4004"
routes:
- path: "/api/search/*"
- path: "/api/suggest/*"
# Main web app
- name: web-app
target: "http://localhost:4000"
default: true
health:
path: /health
interval: 30sQuickly switch between different backend environments.
Use Case: Switch between local, staging, and production APIs
# hz.yaml (local development)
services:
- name: api
target: "http://localhost:3001"
default: true# hz.staging.yaml
services:
- name: api
target: "https://staging-api.example.com"
default: true# hz.prod.yaml (read-only testing)
services:
- name: api
target: "https://api.example.com"
default: trueUsage:
# Local development
hz start
# Against staging
hz start -c hz.staging.yaml
# Against production (careful!)
hz start -c hz.prod.yamlTest how your frontend handles multiple backend instances.
Use Case: Simulate multiple backend instances
# hz.yaml
services:
- name: backend-1
target: "http://localhost:3001"
routes:
- header: "x-instance=1"
- name: backend-2
target: "http://localhost:3002"
routes:
- header: "x-instance=2"
- name: backend-3
target: "http://localhost:3003"
routes:
- header: "x-instance=3"
- name: default-backend
target: "http://localhost:3001"
default: trueTesting:
# Test specific instance
curl -H "x-instance: 2" http://localhost:3000/api/test
# Default routing
curl http://localhost:3000/api/testMonitor service health with automatic status tracking.
Configuration:
# hz.yaml
services:
- name: api
target: "http://localhost:3001"
default: true
health:
path: /health
interval: 30s
timeout: 5s
- name: database-api
target: "http://localhost:3002"
routes:
- path: "/db/*"
health:
path: /ping
interval: 10s
timeout: 2sCheck Status:
hz status
# Output:
# Service Status:
# • api → healthy (localhost:3001)
# • database-api → unhealthy (localhost:3002)
#
# Last check: 2024-01-15 10:30:45
hz status --json # JSON output for scriptingFull configuration file (hz.yaml):
version: "1"
server:
port: 3000 # Proxy listen port
host: "0.0.0.0" # Bind address
readTimeout: 30s # Request read timeout
writeTimeout: 30s # Response write timeout
tunnel:
enabled: false # Enable ngrok tunnel
provider: ngrok # Tunnel provider
authtoken: "${NGROK_AUTHTOKEN}" # Auth token (env var)
domain: "myapp.ngrok.io" # Custom domain (optional)
region: "us" # ngrok region
services:
- name: service-name # Unique service identifier
target: "http://localhost:3001" # Backend URL
default: false # Is default service?
routes:
- path: "/api/*" # Path pattern
- header: "x-service=name" # Header match
- subdomain: "api" # Subdomain match
- method: "POST" # HTTP method filter
- priority: 10 # Route priority (higher wins)
rewrite:
stripPrefix: "/api" # Remove prefix before forwarding
headers:
X-Custom-Header: "value" # Add custom headers
health:
path: /health # Health check endpoint
interval: 30s # Check interval
timeout: 5s # Request timeout
logging:
level: info # Log level: debug, info, warn, error
format: text # Log format: text, jsonCreate a new configuration file:
hz init # Create hz.yaml
hz init --force # Overwrite existingStart the proxy server:
hz start # Start with defaults
hz start -p 8080 # Custom port
hz start --no-tunnel # Disable tunnel
hz start -c custom.yaml # Custom config file
hz start -w # Watch for config changes (default)Add a service to configuration:
hz add <name> <port|url> [flags]
# Examples
hz add backend 3001 --default
hz add api http://localhost:8080 --route '/api/*'
hz add ws 9000 --route 'header:upgrade=websocket'
hz add admin 3002 --route 'subdomain:admin'
hz add mobile 3003 --route 'header:x-client=mobile'Remove a service:
hz remove <name>
hz rm backendShow proxy status:
hz status # Formatted output
hz status --json # JSON outputConfigure ngrok tunnel:
hz tunnel # Show current config
hz tunnel --enable # Enable tunnel
hz tunnel --disable # Disable tunnel
hz tunnel --domain myapp.ngrok.io # Set custom domain
hz tunnel --token YOUR_TOKEN # Set auth token┌─────────────────────────────────────────────────────────────┐
│ hz Proxy Server │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Router │──│ Proxy │──│ Registry │──│ Config │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │ │
│ │ │ │ │ │
│ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐ │
│ │ Path │ │ HTTP │ │ Health │ │ Hot │ │
│ │ Header │ │ WS │ │ Checks │ │ Reload │ │
│ │ Subdmn │ │ Forward │ │ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
├─────────────────────────────────────────────────────────────┤
│ ngrok Tunnel (optional) │
└─────────────────────────────────────────────────────────────┘
hz/
├── main.go # Entry point
├── cmd/hz/ # CLI commands
│ ├── root.go # Root command setup
│ ├── start.go # Start server command
│ ├── add.go # Add service command
│ ├── remove.go # Remove service command
│ ├── status.go # Status command
│ ├── tunnel.go # Tunnel config command
│ └── init.go # Init command
├── internal/
│ ├── config/ # Configuration management
│ ├── proxy/ # HTTP/WebSocket proxy
│ ├── registry/ # Service registry
│ ├── router/ # Route matching
│ └── tunnel/ # ngrok integration
└── pkg/types/ # Shared types
MIT License - see LICENSE for details.
See CONTRIBUTING.md for contribution guidelines.
