Case · Go backend
Sluice: API gateway
An API gateway on the pure stdlib: host/prefix routes from a JSON config with hot reload that keeps connections alive, round-robin and least-conn balancing, health checks, a per-node circuit breaker, retries of idempotent requests, rate limiting, hashed API keys, Prometheus metrics and a live SSE dashboard.
- Go
- stdlib only
- Reverse proxy
- Circuit breaker
- Token bucket
- SSE
- Prometheus
- slog
- Vanilla JS
- embed
- Docker
- GitHub Actions
01 · How it looks
Screenshots


02 · About
What it is and why
Sluice is an API gateway (a reverse proxy) on the pure Go standard library, with no external dependencies. Routes by host and prefix are described in a JSON config that reloads on the fly without dropping connections.
Each route points to an upstream with several nodes: round-robin or least-conn balancing, health checks, a circuit breaker per node, and retries of idempotent requests. On top of that come a rate limit, hashed API keys, Prometheus metrics and a live SSE dashboard.
In demo mode the process starts three fake upstreams and a synthetic traffic generator that goes through the real gateway. These are not real services and not real load.
Limitations
The rate limit and circuit breaker state live in the memory of one process, so with several replicas the limits are not shared. There is no TLS on the inbound side, and counters are not kept across restarts.
WebSocket and Upgrade are not supported (501); HTTP/2 to upstreams and gRPC were not tested. SIGHUP works on Unix only; on Windows there is POST /api/reload.
Percentiles are computed from a histogram with a x1.25 step, so the error is up to 25% upward. The admin uses a single token, with no roles or audit log.
The demo key demo-key-not-a-secret is public. The load figure comes from one machine and is not a benchmark.
What is inside
- Routing by host and longest prefix; an unknown route gets 404, and the gateway is not an open proxy (the destination comes only from the config)
- Hot reload: the state is immutable and swapped with a single atomic.Pointer, the listening socket is untouched, and an invalid config is rejected as a whole
- Round-robin and least-conn balancing over healthy nodes only, with a health loop per node
- A circuit breaker per node (closed, open, half-open) and retries on another node for idempotent methods; the body is buffered up to 1 MiB
- SSRF protection: upstream addresses are checked at load time and again at connect time (DNS rebinding)
- Token-bucket rate limit (per IP or key), API keys stored as sha256, Prometheus metrics, and an SSE dashboard in vanilla JS
- A load test inside the test suite: 4000 requests, 40 clients, about 12,000 requests per second (a demo measurement on one machine, not a benchmark)
- Docker and GitHub Actions (go test -race in CI)
03 · How to run
Run it locally
git clone <repository on request> api-gateway
cd api-gateway
SEED_DEMO=1 go run ./cmd/gateway # proxy :8087, dashboard http://127.0.0.1:8088
go run ./cmd/gateway -genkey # a new API key and its sha25604 · Code
The repository is private
The source lives in a private repository. I will grant access on request: write to me and I will send an invitation.
Request the code