A developer-first HTTP egress proxy platform for managing, monitoring, and controlling outbound API calls. Built for teams making expensive external API requests (OpenAI, Twilio, payment processors, etc.), EPC provides rate limiting, circuit breaking, request deduplication, cost tracking, and full observability out of the box.
- Architecture Overview
- Tech Stack
- Repository Structure
- Features
- Getting Started
- Environment Variables
- API Reference
- SDK Usage
- CLI Usage
- Observability
- Authentication & Authorization
- Database Schema
- CI/CD
- Contributing
- License
+-------------------+
| React Console | :5173
| (Vite + TS) |
+--------+----------+
|
v
+----------+ +-------------------+ +------------------+
| SDK / | ----> | Egress Proxy | ----> | External APIs |
| CLI | | (Fastify) | | (OpenAI, Twilio, |
+----------+ | :8080 | | Stripe, etc.) |
+----+---------+----+ +------------------+
| |
+---------+ +---------+
v v
+------------------+ +------------------+
| PostgreSQL 15 | | Redis 7 |
| :5432 | | :6379 |
| | | |
| - Request logs | | - Rate limits |
| - Domain configs | | - Circuit state |
| - Service keys | | - Dedup cache |
| - Audit trail | | - Key cache |
| - Secrets vault | | |
+------------------+ +------------------+
|
+---------+---------+
v v
+----------+ +-------------+ +-----------+
| pgAdmin | | Prometheus | | Grafana |
| :5050 | | :9090 | | :3000 |
+----------+ +-------------+ +-----------+
| Layer | Technology |
|---|---|
| Backend | Node.js 20, Fastify 4, TypeScript, Zod |
| Database | PostgreSQL 15 (Drizzle ORM), 5 migrations |
| Cache | Redis 7 (ioredis) |
| Frontend | React 18, React Router 6, Vite 5, Three.js, CSS |
| SDK | TypeScript, zero dependencies, drop-in fetch() replacement |
| CLI | TypeScript, zero runtime dependencies |
| Auth | JWT (admin), SHA-256 hashed service keys, TOTP 2FA, Argon2 |
| Observability | Pino (logging), Prometheus + Grafana (metrics), OpenTelemetry |
| Infra | Docker Compose (Postgres, Redis, pgAdmin, Prometheus, Grafana) |
| CI | GitHub Actions |
Egress/
├── server/ # Fastify backend
│ ├── src/
│ │ ├── server.ts # Entry point, graceful shutdown
│ │ ├── app.ts # App setup, route registration
│ │ ├── config/ # Zod-validated env config
│ │ ├── lib/ # Core utilities
│ │ │ ├── db/ # Drizzle schema + migrations
│ │ │ ├── redis.ts # Redis singleton
│ │ │ ├── rate-limiter.ts # Token bucket (atomic Lua)
│ │ │ ├── circuit-breaker.ts # 3-state machine
│ │ │ ├── deduplicator.ts # Content-hash cache
│ │ │ ├── forwarder.ts # HTTP client (undici)
│ │ │ ├── metrics.ts # Prometheus counters/histograms
│ │ │ ├── logger.ts # Pino async batched logger
│ │ │ ├── jwt.ts # JWT signing/verification
│ │ │ ├── crypto.ts # Key generation, hashing
│ │ │ ├── mailer.ts # Nodemailer SMTP
│ │ │ └── ...
│ │ ├── plugins/ # auth, observability, RBAC
│ │ └── modules/ # Feature modules
│ │ ├── proxy/ # Main forwarding pipeline
│ │ ├── services/ # Service registry CRUD
│ │ ├── config/ # Domain configuration
│ │ ├── circuit/ # Circuit breaker management
│ │ ├── analytics/ # Cost, latency, request analytics
│ │ ├── auth/ # Login, signup, 2FA, invites
│ │ ├── secrets/ # Encrypted vault
│ │ ├── transforms/ # Request transformations
│ │ ├── webhooks/ # Webhook delivery
│ │ ├── mocks/ # Mock response fixtures
│ │ └── ...
│ ├── test/unit/ # Vitest unit tests
│ ├── Dockerfile # Multi-stage Node 20 Alpine
│ └── .env.example
│
├── client/ # React + Vite console
│ ├── src/
│ │ ├── App.tsx # Route definitions
│ │ ├── api/client.ts # Typed API wrapper
│ │ ├── components/ # Layout, Toast, CommandPalette, ParticleNetwork
│ │ └── pages/ # 19 route pages
│ └── .env.example
│
├── sdk/ # @egress-proxy/sdk
│ └── src/index.ts # EgressClient (zero deps)
│
├── cli/ # @egress-proxy/cli
│ └── src/index.ts # egress-proxy command
│
├── ops/ # Infrastructure config
│ ├── prometheus/ # Scrape config
│ ├── grafana/ # Provisioned dashboards + datasources
│ └── pgadmin/ # Pre-registered server
│
├── docker-compose.yml # Full infra stack
├── DOCUMENTATION.md # Complete architecture + API reference
├── PROGRESS.md # Project status + verified tests
└── .github/workflows/ci.yml # CI pipeline
- Rate Limiting - Atomic token-bucket algorithm (Lua script) per domain, multi-instance safe
- Circuit Breaker - 3-state machine (closed / open / half-open), shared via Redis, configurable threshold and reset timeout
- Retries - Configurable max attempts with exponential, linear, or no backoff + optional jitter
- Request Deduplication - Content-hash based with configurable TTL (default 100ms)
- Timeout - Per-domain and per-path configurable timeouts
- Priority Queueing - High-priority requests skip the queue; low-priority fail fast
- Per-service x domain usage tracking
- Pricing rules for provider-specific models (OpenAI tokens, Twilio segments, etc.)
- Monthly budget caps per service
- Dedup savings analytics
- JWT authentication for admin users with RBAC (admin, editor, viewer)
- SHA-256 hashed service keys with Redis caching
- TOTP 2FA for admin accounts
- Brute-force login protection (9 attempts / 5 min)
- Encrypted secrets vault (AES, key decoupled from JWT)
- Helmet middleware, CORS, graceful shutdown
- Dashboard - 24h overview with request rate, error rate, latency percentiles, circuit states
- Services - Register services, reveal one-time keys, rotate, delete
- Domains - Full CRUD for rate limits, circuit breaker, retry, timeout
- Circuits - Live state per domain, event history, manual reset
- Forwarder - Interactive request builder with full response inspection
- Analytics - Tabbed views: requests, latency, failures, dedup, cost
- Secrets - Encrypted vault management
- Transforms - Header injection, URL rewrite with secret interpolation
- Mocks - Mock response fixtures for dev/test
- Webhooks - Rule-based alerting
- Audit Log - Configuration change history
- Admin Management - User CRUD, role assignment, invite flow
- Account - Profile settings, TOTP 2FA setup
- Command Palette - Keyboard-accessible navigation (Ctrl+K)
- 3D Particle Network - Three.js visualization on login
- SDK - Zero-dependency TypeScript SDK, drop-in
fetch()replacement - CLI - Command-line management tool for services, domains, secrets
- Request Replay - Re-forward historical requests from the UI
- Live Tail - WebSocket streaming of real-time request logs
git clone https://github.com/YourUsername/Egress.git
cd Egressdocker compose up -dThis starts PostgreSQL, Redis, pgAdmin, Prometheus, and Grafana.
cd server
npm install
cp .env.example .env # Edit if needed (defaults work for local dev)
npx drizzle-kit generate # Only if schema changed
npm run db:migratecd ../client
npm install
cp .env.example .env# Terminal 1 - Backend (http://localhost:8080)
cd server && npm run serve
# Terminal 2 - Frontend (http://localhost:5173)
cd client && npm run dev- Open http://localhost:5173
- Click Sign Up to create the first admin account and tenant
- Create your first service from the Services page
- Save the one-time service key (it won't be shown again)
- Configure domain rules from the Domains page
| Service | URL | Credentials |
|---|---|---|
| Egress Proxy | http://localhost:8080 | X-Service-Key header |
| Console UI | http://localhost:5173 | Admin login |
| pgAdmin | http://localhost:5050 | admin@example.com / admin |
| Prometheus | http://localhost:9090 | -- |
| Grafana | http://localhost:3000 | admin / admin |
| PostgreSQL | localhost:5432 | egress / egress |
| Redis | localhost:6379 | -- |
| Variable | Required | Default | Description |
|---|---|---|---|
NODE_ENV |
No | development |
development, production, or test |
PORT |
No | 8080 |
Server port |
HOST |
No | 0.0.0.0 |
Server host |
LOG_LEVEL |
No | info |
Pino log level |
DATABASE_URL |
Yes | -- | PostgreSQL connection string |
REDIS_URL |
No | redis://localhost:6379 |
Redis connection string |
JWT_SECRET |
Prod | Auto-generated in dev | Min 32 chars; required in production |
JWT_EXPIRES_IN |
No | 24h |
JWT token expiration |
SECRETS_ENCRYPTION_KEY |
Prod | Falls back to JWT_SECRET | Vault encryption key (min 32 chars) |
CORS_ORIGINS |
No | http://localhost:5173 |
Comma-separated allowed origins |
DEFAULT_REQUEST_TIMEOUT_MS |
No | 10000 |
Default proxy request timeout |
DEDUP_TTL_MS |
No | 100 |
Deduplication cache TTL |
LOG_BATCH_SIZE |
No | 100 |
Async log batch size |
LOG_FLUSH_INTERVAL_MS |
No | 250 |
Log flush interval |
REQUEST_LOG_RETENTION_DAYS |
No | 30 |
Request log retention period |
SERVICE_KEY_CACHE_TTL_S |
No | 300 |
Redis service key cache TTL |
SMTP_URL |
No | -- | SMTP connection (logs to stdout if unset) |
EMAIL_FROM |
No | noreply@egress.local |
Sender address for emails |
APP_URL |
No | http://localhost:5173 |
Frontend URL (used in emails) |
| Variable | Required | Default | Description |
|---|---|---|---|
VITE_API_URL |
Yes | -- | Backend API base URL |
GET /health/live # Liveness probe
GET /health/ready # Readiness probe (checks DB + Redis)
POST /proxy/forward # Forward a request through the pipeline
Request body:
{
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"headers": { "Authorization": "Bearer sk-..." },
"body": { "model": "gpt-4o-mini", "messages": [...] },
"timeout": 15000,
"priority": "high"
}Response:
{
"status": 200,
"headers": { "content-type": "application/json" },
"body": { "choices": [...] },
"meta": {
"request_id": "uuid",
"latency_ms": 234,
"attempts": 1,
"cached": false,
"circuit": "closed",
"cost_cents": 12
}
}POST /services # Create service (first call is bootstrap)
GET /services # List services
DELETE /services/:id # Delete service
POST /services/:id/rotate-key # Rotate service key
GET /config/domains # List domain configs
GET /config/domains/:domain # Get domain config
POST /config/domains # Create domain config
PATCH /config/domains/:domain # Update domain config
DELETE /config/domains/:domain # Delete domain config
GET /circuit-breakers # List all circuit states
GET /circuit-breakers/:domain # Get circuit state for domain
POST /circuit-breakers/:domain/reset # Manually reset circuit
GET /analytics/requests # Request volume over time
GET /analytics/latency # Latency percentiles
GET /analytics/failures # Failure breakdown
GET /analytics/circuit-events # Circuit state transitions
GET /analytics/cost # Cost per service x domain
GET /analytics/dedup # Dedup hit rate
POST /auth/signup # Create first admin + tenant
POST /auth/login # Admin login (returns JWT)
POST /auth/forgot-password # Password reset request
POST /auth/reset-password # Reset with token
POST /auth/invite # Invite user to tenant
GET /secrets # List secrets (names only)
POST /secrets # Create encrypted secret
PATCH /secrets/:id # Update secret
DELETE /secrets/:id # Delete secret
GET/POST/PATCH/DELETE /transforms # Request transformation rules
GET/POST/PATCH/DELETE /mocks # Mock response fixtures
GET/POST/PATCH/DELETE /webhooks # Webhook delivery targets
Full API documentation with schemas is in DOCUMENTATION.md.
Install the SDK in your application:
npm install @egress-proxy/sdkimport { EgressClient } from '@egress-proxy/sdk';
const egress = new EgressClient({
proxyUrl: 'http://localhost:8080',
serviceKey: process.env.EGRESS_PROXY_KEY!,
});
// Use like native fetch() - all requests go through the proxy
const res = await egress.fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.OPENAI_KEY}` },
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [{ role: 'user', content: 'hello' }],
}),
});
const data = await res.json();
// Proxy metadata available in response headers
console.log(res.headers.get('x-egress-latency-ms')); // "234"
console.log(res.headers.get('x-egress-attempts')); // "1"
console.log(res.headers.get('x-egress-cost-cents')); // "12"const r = await egress.forward('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.OPENAI_KEY}` },
body: JSON.stringify({ model: 'gpt-4o-mini', messages: [...] }),
});
console.log(r.meta.latency_ms); // 234
console.log(r.meta.attempts); // 1
console.log(r.meta.circuit); // "closed"
console.log(r.meta.cost_cents); // 12# Login
egress-proxy login admin@example.com
# Services
egress-proxy services list
egress-proxy services create --name order-service --budget 10000
egress-proxy services rotate <service-id>
# Domains
egress-proxy domains list
egress-proxy domains create --domain api.openai.com --rate 60 --window 1m
# Secrets
egress-proxy secrets set --name openai_prod --value "sk-..."
# Status
egress-proxy status| Metric | Type | Description |
|---|---|---|
egress_forwarded_requests_total |
Counter | Requests by domain, service, status |
egress_request_latency_ms |
Histogram | Latency distribution (p50/p95/p99) |
egress_circuit_breaker_state |
Gauge | 0=closed, 1=open, 2=half-open |
egress_dedup_cache_hits |
Counter | Deduplicated request count |
egress_rate_limit_queue_depth |
Gauge | Rate limiter queue depth |
Two dashboards are provisioned automatically at boot:
- Egress Proxy - Request rate by domain/status, latency percentiles, circuit breaker state, error rate, dedup savings, process memory
- Egress Proxy Per-Service - Service-level breakdowns
Structured JSON logging via Pino with async batched writes (100 logs/batch, 250ms flush). Never blocks the request path.
- JWT tokens issued at login, stored in HTTP-only cookies
- 24h expiration (configurable)
- RBAC roles:
admin,editor,viewer - Optional TOTP 2FA
- Brute-force protection: 9 attempts per 5-minute window
- SHA-256 hashed (plaintext never stored)
- Passed via
X-Service-Keyheader - Cached in Redis (300s TTL)
- Per-service: rate limits, IP allowlist, domain scopes, expiration, monthly budget
Multi-tenant PostgreSQL schema with the following primary tables:
| Table | Purpose |
|---|---|
tenants |
Multi-tenancy root, workspace isolation |
users |
Admin users per tenant, password + TOTP 2FA |
services |
Service registry, hashed API keys, budgets |
secrets |
Encrypted vault for upstream credentials |
domain_configs |
Rate limits, circuit breaker, retry, timeout settings |
path_configs |
Path-scoped overrides on domain configs |
transforms |
Request transformations (header inject, URL rewrite) |
mocks |
Mock response fixtures |
webhooks |
Webhook delivery targets + trigger rules |
pricing |
Cost rules per provider/model |
request_logs |
Full request/response audit trail (30-day retention) |
circuit_events |
Circuit breaker state transitions |
audit_log |
Admin action audit trail |
GitHub Actions runs on push/PR to main:
| Job | Steps |
|---|---|
server |
Build, typecheck, test (with Postgres + Redis) |
client |
Build, typecheck |
sdk |
Build |
cli |
Build |
cd server
npm test # Runs 11 unit tests via VitestTests cover:
- Configuration schema validation
- Deduplicator content hashing
- Rate limiter token bucket logic
- Circuit breaker state transitions
| # | Test | Result |
|---|---|---|
| 1 | Health live + ready | Redis + DB reachable |
| 2 | Bootstrap service registration | Key returned |
| 3 | Auth rejects missing key | HTTP 401 |
| 4 | Domain config upsert | Persisted, cached |
| 5 | Proxy forward to httpbin | HTTP 200, attempts=1, circuit=closed |
| 6 | Retry on 503 | 3 attempts, surfaced as BadGateway |
| 7 | Deduplication | 9ms cached vs 1580ms first request |
| 8 | Circuit breaker opens | 4th call blocked with CircuitOpen |
| 9 | Manual circuit reset | State returned to closed |
| 10 | Analytics endpoints | All 6 returning aggregates |
| 11 | Prometheus scrape | Counters visible |
| 12 | Grafana dashboard | Datasource + dashboard provisioned |
| 13 | Unit tests | 11/11 passing |
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is provided as-is for educational and development purposes.